18

I need to write a function that will concatenate a list into a string. example:

(concatString (quote ("hello" " world"))) ==> "hello world"

here is what i have so far:

(defun concatString (list)
  "A non-recursive function that concatenates a list of strings."
  (cond
   ((not (listp list))
     (princ "Error: argument to concatNR must be a list")(terpri) ())) ; check if parameter is a list

  (if (not (null list)) ;check if list is not null
      (let ((result (car list)))
        (dolist (item (cdr list))
          (if (stringp item)
              (setq result (concatenate result item)))          
        )
      )
  )
)

I'm getting a "Error: "hello" is and illegal type specifier" message when i try to run it. I've tried a bunch of ways to modify this function and i havent been able to figure it out. does anyone have any ideas?

8 Answers 8

28

concatenate requires a sequence type specifier as its second argument. To concatenate two strings, you should call concatenate as:

(concatenate 'string "hello" "world")

Another bug in your code: you do not make sure that the car of the list is a string before assigning it to result. By fixing your code, I came up with the following implementation:

(defun concatString (list)
  "A non-recursive function that concatenates a list of strings."
  (if (listp list)
      (let ((result ""))
        (dolist (item list)
          (if (stringp item)
              (setq result (concatenate 'string result item))))
        result)))

;; tests
> (concatString (list "hello" " world"))
"hello world"
> (concatString (list "hello" 1 2 3 " world"))
"hello world"
> (concatString (list "hello" 1 2 "3" " world"))
"hello3 world"
> (concatString (list 1 2 3 "hello" " world"))
"hello world"

The following redefinition of concatString is more efficient as it does not create many intermediary string objects:

(defun concatString (list)
  "A non-recursive function that concatenates a list of strings."
  (if (listp list)
      (with-output-to-string (s)
         (dolist (item list)
           (if (stringp item)
             (format s "~a" item))))))
Sign up to request clarification or add additional context in comments.

3 Comments

Im checking if its a string because for the assignment, if its a number it shouldn't be added to the string. Thank you very much though the fix worked!!! =)
That's relatively bad: you are concatenating repeatedly creating new result strings all the time. This possibly creates a large amount of garbage.
@Rainer Joswig How can I fix it?
18

Just use the format function on a list, this will convert everything to strings and concatenate them with the correct format string.

(defun my-concat( list )
  (format nil "~{~a~}" list))

If you want to concatenate them with a space use this form with the "~^" directive:

(defun my-concat( list )
  (format nil "~{~a~^ ~}" list))

If you'd like to filter out the results, you can just transform the list before formatting it.

(defun my-concat(list)
  (format nil "~{~a~^ ~}" (remove-if-not #'stringp list)))

3 Comments

sorry, i should have mentioned that if that it should ignore items in the list that are not strings. so if an item is an number it should not add it to the string.
Yeah, that changes the scope of the problem quite a bit
You can filter out non-strings with remove-if-not: (defun my-concat( list ) (format nil "~{~a~}" (remove-if-not #'stringp list)))
10

To concatenate sequences to a string, use concatenate 'string.

(defun concat-strings (list)
  (apply #'concatenate 'string list))

To remove anything from the list that is not a string, use remove-if-not.

(defun concat-strings (list)
  (apply #'concatenate 'string
         (remove-if-not #'stringp list)))

If the argument is not a list, an error will be signaled by remove-if-not. You can add the assertion before, of course, to give a more specific error message, but it does not really add value here.

(defun concat-strings (list)
  (assert (listp list)
          "This is not a list: ~s." list)
  (apply #'concatenate 'string
         (remove-if-not #'stringp list)))

EDIT:

As Rainer notes, apply only works on lists of limited length. If you do not have reason to believe that your list cannot be longer than call-arguments-limit minus one, a reduce form is better:

(defun concat-strings (list)
  (reduce (lambda (a b)
            (concatenate 'string a b))
          (remove-if-not #'stringp list)))

1 Comment

Revisiting, it should be noted that if you want to concatenate a larger number of strings, it is more efficient to either work with streams (e.g., with-output-to-string), or to preallocate the result string and then fill it.
3

Common Lisp the Language, 2nd Edition

concatenate result-type &rest sequences

This one should work

(concatenate 'string result item)

Comments

3

According to the Common Lisp Cookbook :

(concatenate 'string "Karl" " " "Marx")
"Karl Marx"

Comments

2

Why limit yourself to lists?

(defun concatenate-strings (sequence)
  (reduce #'(lambda (current next)
              (if (stringp next)
                (concatenate 'string current next)
                current))
          sequence
          :initial-value ""))

Comments

2

Here are my two cents:

(defmacro concatString (&rest strings) `(concatenate 'string ,@strings) )

Comments

0

UIOP, a library that pretty much every Common Lisp implementation still in use ships with, has a function for concatenating a list of strings: reduce/strcat.

CL-USER> (uiop:reduce/strcat '("hello" " world"))
"hello world"

In the case of wanting to filter out non-strings from a list, its :key argument can be used to check each element, instead of building up a new list with remove-if-not or whatever:

CL-USER> (uiop:reduce/strcat '("hello" 2 " world") :key (lambda (s) (if (stringp s) s "")))
"hello world"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.