3

I'm pretty new to Lisp and I have the following problem.

I'm trying to swap the numbers around in multiple lists and produce a list of lists as the result, so that all the numbers that were the 1st number in each list will all be collected in a list which is the 1st element of the result list; and all the numbers that were 2nd in each list will be collected in another list which is the 2nd element of the result list; and so on.

Here is an example:

(foo '((3 4 2 4) (2 5 6 9) (1 -2 8 10)) ) 
=> ((3 2 1) (4 5 -2) (2 6 8) (4 9 10))

My current solution is this:

(defun foo (list)
    (mapcar #'list (values-list list))
)

As far as I understand it, values-list should return all sublists from inside the given parameter list. Then mapcar will go through each element of each sublist and make them into a list.

This second part works, however what happens is that only the first sublist is used, resulting in ((3) (4) (2) (4)) instead of ((3 2 1) (4 5 -2) (2 6 8) (4 9 10)).

Why is this?

(I'm using CLISP).

Will Ness
  • 70,110
  • 9
  • 98
  • 181
BenKlee
  • 43
  • 3

2 Answers2

3

In your code

(defun foo (list)
  (mapcar #'list (values-list list)))

values-list returns the list as multiple values. But you are calling a function like mapcar and a normal function call takes only one value per argument.

To repair that, you could call mapcar via multiple-value-call:

CL-USER 47 > (defun foo (list)
               (multiple-value-call #'mapcar #'list (values-list list)))
FOO

CL-USER 48 > (foo '((3 4 2 4) (2 5 6 9) (1 -2 8 10)) )
((3 2 1) (4 5 -2) (2 6 8) (4 9 10))

Above then will pass all values to mapcar.

Note that argument lists are limited in Common Lisp implementations. See the variable call-arguments-limit. One can only be sure that function calls with less than call-arguments-limit arguments work in a given implementation of Common Lisp.

Thus for list processing with unbounded length lists one should not depend on features like apply or multiple-value-call.

Using Multiple Values

Common Lisp supports multiple values as a kind of optimization. It allows one to return multiple values without the need to construct a data structure for it.

Generally functions only receive single values per argument.

Everything concerned with multiple values is done via operators: values returns values, multiple-value-bind receives values and binds them to variables, ... Then there is multiple-value-call, values-list, multiple-values-limit, multiple-value-list, multiple-value-setq, (setf (values ...) ...), multiple-value-prog1, nth-value.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • 1
    Thank you! I didn't think values would be treated differently from normal arguments so I thought mapcar would just take each value as an argument. – BenKlee Dec 12 '19 at 20:43
2

What you are trying to do is transpose a matrix as a list of lists. A standard way of doing this in Common Lisp is with the following function:

CL-USER> (defun transpose (list)
           (apply #'mapcar #'list list))
TRANSPOSE
CL-USER> (transpose '((3 4 2 4) (2 5 6 9) (1 -2 8 10)))
((3 2 1) (4 5 -2) (2 6 8) (4 9 10))

What values-list does is to return all the elements of a list as different result values, in order:

CL-USER> (values-list '((3 4 2 4) (2 5 6 9) (1 -2 8 10)))
(3 4 2 4)
(2 5 6 9)
(1 -2 8 10)

So your function takes the main value returned (the first list) and applies list to all its elements.

Note how the transpose function works when applied to a list argument like '((3 4 2 4) (2 5 6 9) (1 -2 8 10)): it “transforms” (in some sense) the call in something like:

(mapcar #'list '(3 4 2 4) '(2 5 6 9) '(1 -2 8 10))

In fact if you call directly this form it will produce the correct result:

CL-USER> (mapcar #'list '(3 4 2 4) '(2 5 6 9) '(1 -2 8 10)) 
((3 2 1) (4 5 -2) (2 6 8) (4 9 10))
Renzo
  • 26,848
  • 5
  • 49
  • 61