SigTerm
SigTerm

Reputation: 26409

Common Lisp: concatenate multiple values into vector

I need a function that concatenates multiple values into (simple) vector, similar to (concatenate ). However, unlike concatenate, it should be able to handle arguments that are not vectors or sequences.

I.e. it should work like this:

(concat #(1 2) 3) => #(1 2 3)
(concat 1 2 3) => #(1 2 3)
(concat 1 #(2 3 4)) => #(1 2 3 4)
(concat #(1 2) 2 #(3 4 5)) => #(1 2 3 4 5)

How can I do this? I think I've forgotten some trivial lisp construct that makes it possible.

As far as I can tell, concatenate can't do it. and I'm not quite sure how to use make it with macro (there's ,@ consturct that inserts list into resulting lisp form, but but I'm not quite sure how to distinguish between non-sequences and sequences in this case).

Upvotes: 4

Views: 1276

Answers (3)

Rainer Joswig
Rainer Joswig

Reputation: 139261

Since we can compute the length of the sequence, we can allocate the result sequence and then copy the elements into it.

(defun concat (type &rest items)
  (let* ((len (loop for e in items
                    if (typep e 'sequence)
                    sum (length e)
                    else sum 1))
         (seq (make-sequence type len)))
    (loop with pos = 0
          for e in items
          if (typep e 'sequence)
          do (progn
               (setf (subseq seq pos) e)
               (incf pos (length e)))
          else
          do (progn
               (setf (elt seq pos) e)
               (incf pos)))
    seq))


CL-USER 17 > (concat 'string "abc" #\1 "def" #\2)
"abc1def2"

Above works well for vectors. A version for lists is left as an exercise.

Upvotes: 3

Haile
Haile

Reputation: 3180

defun my-concatenate (type &rest vectors)
  (reduce (lambda (a b)
            (concatenate
             type 
             (if (typep a 'sequence) a (list a))
             (if (typep b 'sequence) b (list b))))
          vectors))

You can use reduce with a little modification of #'concatenate on your arguments. If one of the arguments is not a sequence, just transform it into a list (concatenate works even with mixed arguments of simple-vectors and lists).

CL-USER> (my-concatenate 'list #(1 2 3) 3 #(3 5))
   (1 2 3 3 3 5)

CL-USER> (my-concatenate 'simple-vector #(1 2 3) 3 #(3 5))
   #(1 2 3 3 3 5)

CL-USER> (my-concatenate 'simple-vector 1 #(2 3) (list 4 5))
   #(1 2 3 4 5)

EDIT: well, you should probably accept the other answer.

Upvotes: 1

sds
sds

Reputation: 60014

The reduce approach in the other reply is quadratic in time.

Here is a linear solution:

(defun my-concatenate (type &rest args)
  (apply #'concatenate type
         (mapcar (lambda (a) (if (typep a 'sequence) a (list a)))
                 args)))

Upvotes: 5

Related Questions