Транспонирование списков в Common Lisp

Я пытаюсь перенести список списков; мои комментарии указывают на мыслительный процесс.

(setq thingie  '((1 2 3) (4 5 6) (7 8 9)))  ;;test case

(defun trans (mat)
  (if (car mat)
    (let ((top (mapcar 'car  mat))   ;;slice the first row off as a list
          (bottom (mapcar 'cdr mat))) ;;take the rest of the rows
      (cons top (trans bottom))))    ;;cons the first-row-list with the next-row-list
   mat)

(trans thingie)
=> ((1 2 3) (4 5 6) (7 8 9))           ;;wait what? 

Но я действительно хочу, чтобы это было

((1 4 7) (2 5 8) (3 6 9))

Что я делаю неправильно?

Ответ 1

Для этого существует простой способ:

(defun rotate (list-of-lists)
  (apply #'mapcar #'list list-of-lists))

Ваша попытка всегда возвращает оригинал mat. Исправьте свой отступ, и вы увидите, что возвращаемое значение из формы if всегда выбрасывается.

Изменить: как это работает:

  • List принимает любое количество аргументов и составляет его список. Его определение функции можно представить примерно так:

    (defun list (&rest arguments)
      arguments) ; exploit the automatic &rest construction
    
  • Mapcar принимает функцию и любое количество списков, а затем делает новый список значений, созданных при вызове функции всегда с один элемент из этих списков. Пример: (mapcar #'foo '((A B) (C D))) построит новый список, где первым элементом является результат (foo 'A 'C), а второй результат (foo 'B 'D).

  • Apply принимает расширительный список списков аргументов в качестве последнего аргумент. Это означает, что если вы дадите ему список в качестве последнего аргумент, этот список может быть "распространен" для создания отдельных аргументов для функции. Пример: (apply #'+ '(1 2 3)) имеет тот же эффект как (+ 1 2 3).

Теперь вы можете развернуть строку:

(apply #'mapcar #'list '((A B) (C D)))

= >

(mapcar #'list '(A B) '(C D))

= >

(list (list 'A 'C) (list 'B 'D))

= >

'((A C) (B D))