Listas

Montando listas, Cons cells e listas

Uma lista também é uma sequência, portanto é possível usar as funções abaixo.

Cons cells são os elementos básicos de uma lista. Listas são construídas agrupando-se cons cells.

(cons 1 2)
;; => (1 . 2) ;; representação com um ponto, um dotted pair.

Assim:

[o|o]--- 2
 |
 1

Se o cdr da primeira cell for outra cons cell, e se o cdr desta última cons cell for nil, uma lista é montada:

(cons 1 (cons 2 nil))
;; => (1 2)

Mais ou menos assim:

[o|o]---[o|/]
 |       |
 1       2

(ascii art criada com draw-cons-tree).

Percebeu como a lista não foi representada como um dotted pair? O printer entende a convenção.

Por fim, é possível construir uma lista literal usando list:

(list 1 2)
;; => (1 2)

ou usando uma aspa simples:

'(1 2)
;; => (1 2)

que nada mais é do que uma simplificação para a chamada de função (quote (1 2)).

Listas Circulares

O car ou o cdr de uma cons cell pode referenciar outros objetos, incluindo a si mesmo ou a outras cells da mesma lista. Portanto car e cdr podem ser usados para definir estruturas de auto-referência, tais como listas circulares.

Antes de começar a trabalhar com listas circulares, avise ao printer, para que ele as reconheça e que não tente imprimir a lista completa setando *print-circle* para T`:

(setf *print-circle* t)

Uma função que modifica uma lista, de forma que o último cdr aponte para o começo da lista é:

(defun circular! (items)
  "Modifica o último cdr de uma lista ITEMS, retornando uma lista circular"
  (setf (cdr (last items)) items))
(circular! (list 1 2 3))
;; => #1=(1 2 3 . #1#)

(fifth (circular! (list 1 2 3)))
;; => 2

A função list-length reconhece uma lista circular, retornando nil.

O reader também pode criar uma lista circular, usando a notação Sharpsign Equal-Sign Um objeto, (como uma lista) pode ser prefixado com #n= onde n é um número inteiro decimal sem sinal (um ou mais dígitos). O label #n# pode ser depois ser usado para referenciar o objeto na expressão:

'#42=(1 2 3 . #42#)
;; => #1=(1 2 3 . #1#)

Note que o label dado ao reader (n=42) é descartado após a leitura, e que o printer define um novo label (n=1).

Leitura complementar

car/cdr ou primeiro/resto (e segundo... ao décimo)

(car (cons 1 2)) ;; => 1
(cdr (cons 1 2)) ;; => 2
(first (cons 1 2)) ;; => 1
(first '(1 2 3)) ;; => 1
(rest '(1 2 3)) ;; => (2 3)

É possível atribuir qualquer novo valor usando setf

last, butlast, nbutlast (&optional n)

retorna a última cons cell em uma lista (ou a n-ésima última cons cell).

(last '(1 2 3))
;; => (3)
(car (last '(1 2 3)) ) ;; ou (first (last …))
;; => 3
(butlast '(1 2 3))
;; => (1 2)

Em Alexandria, lastcar é equivalente a (first (last …)):

(alexandria:lastcar '(1 2 3))
;; => 3

reverse, nreverse

reverse e nreverse retorna uma nova sequência.

nreverse é destrutiva. O N significa non-consing, ou seja, não é necessário alocar novas cons cells. Ela pode (e na prática, é isso que ela faz) reusar e modificar a sequência original:

(defparameter mylist '(1 2 3))
;; => (1 2 3)
(reverse mylist)
;; => (3 2 1)
mylist
;; => (1 2 3)
(nreverse mylist)
;; => (3 2 1)
mylist
;; => (1) in SBCL but implementation dependant.

append

append recebe qualquer quantidade de listas como argumento e retorna uma nova lista, contendo os elementos de todos os seus arguments:

(append (list 1 2) (list 3 4))
;; => (1 2 3 4)

A nova lista compartilha de algumas cons cells com a (3 4):

http://gigamonkeys.com/book/figures/after-append.png

Nota: append em cl21 é genérica (para listas, strings, vetores e suas abstract-sequence)

nconc é a equivalente reciclável

push (item, place)

push insere item à lista armazenada em place, guarda a lista resultante em place, e retorna a lista.

(defparameter mylist '(1 2 3))
(push 0 mylist)
;; => (0 1 2 3)
(defparameter x ’(a (b c) d))
;; => (A (B C) D)
(push 5 (cadr x))
;; => (5 B C)
x
;; => (A (5 B C) D)

push é equivalente a (setf place (cons item place)), porém, as subforms de place sofrem eval apenas uma vez, e item tem o eval antes de place.

Não existe uma função nativa para adicionar ao fim de uma lista. Esta é uma operação mais custosa (é preciso percorrer toda a lista). Então, se você precisa realizar essa operação faça o seguinte: Considere utilizar outra esrutura de dados, ou ponha sua lista ao contrário quando necessário.

pop

uma operação destrutiva.

nthcdr (index, list)

Use-a se first, second e todas as outras até tenth não forem o suficiente.

car/cdr e composites (cadr, caadr...) - acessando listas dentro de listas

Essas fazem sentido quando aplicadas a listas que contém outras listas.

(caar (list 1 2 3))                  ==> error
(caar (list (list 1 2) 3))           ==> 1
(cadr (list (list 1 2) (list 3 4)))  ==> (3 4)
(caadr (list (list 1 2) (list 3 4))) ==> 3

destructuring-bind (parameters*, list)

Liga os valores do parâmetro à lista de elementos. É possível desestruturar árvores, plists, e até mesmo prover defaults.

Match simples:

(destructuring-bind (x y z) (list 1 2 3)
  (list :x x :y y :z z))
;; => (:X 1 :Y 2 :Z 3)

Match dentro de sublistas:

(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
  (list :x x :y1 y1 :y2 y2 :z z))
;; => (:X 1 :Y1 2 :Y2 20 :Z 3)

A lista de parâmetros pode fazer uso dos parâmetros &optional, &rest e &key.

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
  (list :x x :y1 y1 :y2 y2 :z z))
;; => (:X 1 :Y1 2 :Y2 NIL :Z 3)
(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z))
;; => (:X 3 :Y 2 :Z 1)

O parâmetro &whole está ligado à lista inteira. Ele deve vir à frente e pode ser prosseguido por outros parâmetros.

(destructuring-bind (&whole whole-list &key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z :whole whole-list))
;; => (:X 3 :Y 2 :Z 1 :WHOLE-LIST (:Z 1 :Y 2 :X 3))

Desestruturando uma plist, provendo defaults:

(exemplo retirado de Common Lisp Recipes, de E. Weitz, Apress, 2016)

(destructuring-bind (&key a (b :not-found) c
                     &allow-other-keys)
    ’(:c 23 :d "D" :a #\A :foo :whatever)
  (list a b c))
;; => (#\A :NOT-FOUND 23)

** TODO If this gives you the will to do pattern matching, see pattern matching.

Predicados: null, listp

null é equivalente a not, mas é o mais recomendado.

listp veerifica se um objeto é uma cons cell ou nil.

e predicados de sequência.

idiff, tailp, list*, make-list, fill, revappend, nreconc, consp, atom

(make-list 3 :initial-element "ta")
;; => ("ta" "ta" "ta")
(make-list 3)
;; => (NIL NIL NIL)
(fill * "hello")
;; => ("hello" "hello" "hello")