lisp function to concatenate a list of strings
Asked Answered
D

7

17

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?

Doridoria answered 28/3, 2011 at 9:58 Comment(0)
D
25

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))))))
Dworman answered 28/3, 2011 at 10:9 Comment(3)
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!!! =)Doridoria
That's relatively bad: you are concatenating repeatedly creating new result strings all the time. This possibly creates a large amount of garbage.Tilda
@Rainer Joswig How can I fix it?Dworman
W
17

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)))
Wilonah answered 28/3, 2011 at 10:9 Comment(3)
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.Doridoria
Yeah, that changes the scope of the problem quite a bitWilonah
You can filter out non-strings with remove-if-not: (defun my-concat( list ) (format nil "~{~a~}" (remove-if-not #'stringp list)))Acculturation
E
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)))
Erewhile answered 28/3, 2011 at 11:40 Comment(1)
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.Erewhile
A
3

Common Lisp the Language, 2nd Edition

concatenate result-type &rest sequences

This one should work

(concatenate 'string result item)
Ask answered 28/3, 2011 at 10:7 Comment(0)
D
3

According to the Common Lisp Cookbook :

(concatenate 'string "Karl" " " "Marx")
"Karl Marx"
Defense answered 28/3, 2011 at 10:7 Comment(0)
D
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 ""))
Darleen answered 28/3, 2011 at 10:36 Comment(0)
C
2

Here are my two cents:

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

Condensable answered 29/9, 2015 at 16:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.