Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lisp function to concatenate a list of strings

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?

like image 658
MBU Avatar asked Mar 28 '11 09:03

MBU


2 Answers

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)))
like image 191
Svante Avatar answered Sep 19 '22 07:09

Svante


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))))))
like image 41
Vijay Mathew Avatar answered Sep 20 '22 07:09

Vijay Mathew