Mapear uma função em uma lista de propriedades?

17

P: qual é a maneira idiomática de mapear uma função em uma lista de propriedades?

As várias funções de mapeamento ( mapcare família) mapeiam uma função em uma sequência, como uma lista. Como alguém usa essas funções ao lidar com uma lista de propriedades , ou seja, ao tentar mapear cada uma das propriedades contidas na lista (que seriam todos os outros elementos a partir da primeira)? Parece-me que a função de mapeamento precisaria acessar a lista em pares de elementos, e não como elementos individuais.

Como um exemplo de brinquedo, como alguém pegaria uma lista de propriedades e coletaria todos os valores de propriedades? Se fosse uma lista de associações, seria bem simples:

(mapcar #'cadr '((:prop1 a) (:prop2 b) (:prop3 c))) ;=> (a b c)

Tenho certeza que isso pode ser feito com um loop, mas parece um pouco trabalhoso e estou me perguntando se há uma maneira mais idiomática de fazer isso.

Dan
fonte
Por favor, esclareça se deseja mapear apenas os valores da propriedade (como é o som e qual é o seu mapcarexemplo atual) ou se deseja mapear os pares de símbolo e valor da propriedade. O último é mais geral (mais útil em geral), eu acho.
Tirou
@ Drew: Estou mais interessado no caso geral; o exemplo foi o mais simples que eu consegui pensar. Gostaria de saber como mapear o par propriedade / valor . Se a resposta for "um loop", que assim seja, mas estou me perguntando se há uma solução mais elegante.
Dan
O que você tem no seu exemplo não é uma lista de propriedades. As listas de propriedades têm um número par de elementos, elementos ímpares são nomes de propriedades, elementos pares são valores de propriedades. O que você tem provavelmente seria chamado de árvore (uma lista de listas).
Wvxvw

Respostas:

8

Você provavelmente obterá várias looprespostas de iteração. AFAIK, não há maneira idiomática de fazer isso. Eu nem acho muito comum querer acumular apenas os valores das propriedades (não associá-los às propriedades).

Aqui está uma maneira simples de fazer isso:

(defun prop-values (plist)
  "..."
  (let ((pl    (cdr plist))
        (vals  ()))
    (while pl
      (push (car pl) vals)
      (setq pl  (cddr pl)))
    (nreverse vals)))

Para o caso mais geral, onde você deseja mapear uma função binária sobre uma lista:

(defun map-plist (fn plist)
  "..."
  (let ((pl    plist)
        (vals  ()))
    (while pl
      (push (funcall fn (car pl) (cadr pl)) vals)
      (setq pl (cddr pl)))
    (nreverse vals)))

(setq foo '(a 1 b 2 c 3 d 4 e 5))

(map-plist #'cons foo) ; => ((a . 1) (b . 2) (c . 3) (d . 4) (e . 5))
Desenhou
fonte
13

Provavelmente isso dependeria de uma situação. Em geral, se eu precisar vincular vários valores a vários nomes, usaria uma tabela de hash, mas, se precisar usar uma lista de propriedades, usaria cl-loop. Abaixo estão alguns exemplos:

(cl-loop for (key value) on '(:prop1 a :prop2 b :prop3 c) by 'cddr
         collect value)
;;; (a b c)

E se você tem uma estrutura de dados, você mostra no seu exemplo:

(cl-loop for (key value) in '((:prop1 a) (:prop2 b) (:prop3 c))
         collect value)
;;; (a b c)
wvxvw
fonte
5

Para mim, a resposta é transformar o plist em um alist, que é uma estrutura de dados equivalente, apenas metade da profundidade e mais fácil de manipular.

Stefan
fonte
3
Bem, isso é meio que uma resposta (e um bom conselho), mas mais um comentário.
Tirou
4

Com o Emacs do Git HEAD atual, que se tornará o Emacs 25, você pode fazer o seguinte, ou seja, transformar o plist em um alist facilmente com a nova seq-partitionfunção e depois processar as entradas do alist usando o padrão mapcar.

(let* ((my-plist (list :a 1 :b 2 :c 3 :more (list 4 5 6)))
       (my-alist (seq-partition my-plist 2))
       (my-reverse-alist (mapcar (lambda (entry)
                                   (let ((prop (car entry))
                                         (val  (cadr entry)))
                                     (list val prop)))
                                 my-alist)))
  (message "my-plist: %s\nmy-alist: %s\nmy-reverse-alist: %s"
           my-plist my-alist my-reverse-alist))
;; my-plist: (:a 1 :b 2 :c 3 :more (4 5 6))
;; my-alist: ((:a 1) (:b 2) (:c 3) (:more (4 5 6)))
;; my-reverse-alist: ((1 :a) (2 :b) (3 :c) ((4 5 6) :more))

Dê uma olhada nos seq-partitiondocumentos usando C-h f seq-partition RET.

Tassilo Horn
fonte
1

Uma idéia é usar -map-indexeda partir dashe aplicar a transformação apenas para valores ímpares da lista:

(-non-nil (-map-indexed (lambda (index item) (when (oddp index) item))
  '(a x b y c z))) ; => (x y z)
(-non-nil (--map-indexed (when (oddp it-index) it) '(a x b y c z))) ; => (x y z)

Outra idéia é apenas converter um plist para um hash-table, usando ht<-plistde ht:

(ht-values (ht<-plist '(a x b y c z) 'equal)) ; (z y x)

Observe que a tabela de hash não preserva a ordem dos itens.

Mirzhan Irkegulov
fonte