4

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).

3 Answers 3

5

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)))
Sign up to request clarification or add additional context in comments.

Comments

3

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.

Comments

1
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.

3 Comments

Note that this is a quadratic algorithm to solve a linear problem.
OP wrote "similar to concatenate". Since concatenate is a non destructive function, i provided a non destructive solution. If you don't need to preserve the original vectors and you want a linear algorithm, you can just use append in the lambda.
My linear solution is also non-destructive. The problem with your solution is that you are concatenating arguments two by two.

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.