Mappa una funzione attraverso un elenco di proprietà?


17

D: qual è il modo idiomatico di mappare una funzione attraverso un elenco di proprietà?

Le varie funzioni di mappatura ( mapcare famiglia) mappano una funzione su una sequenza come un elenco. Come si usano queste funzioni quando si ha a che fare con un elenco di proprietà , ovvero quando si cerca di mappare ciascuna delle proprietà contenute nell'elenco (che sarebbe ogni altro elemento a partire dal primo)? Mi sembra che la funzione di mappatura debba accedere all'elenco in coppie di elementi anziché come singoli elementi.

Come esempio giocattolo, come si potrebbe prendere un elenco di proprietà e raccogliere tutti i valori delle proprietà? Se fosse invece un elenco di associazioni sarebbe piuttosto semplice:

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

Sono sicuro che questo potrebbe essere fatto con un ciclo, ma sembra un po 'laborioso e mi chiedo se c'è un modo più idiomatico per farlo.


Si prega di chiarire se si desidera mappare solo i valori delle proprietà (che è quello che sembra, e qual è ciò che fa l' mapcaresempio di alist) o se si desidera mappare le coppie di simboli di proprietà e valore della proprietà. Quest'ultimo è più generale (più generalmente utile), immagino.
Estratto il

@Drew: sono più interessato al caso generale; l'esempio era il più semplice a cui potevo pensare. Vorrei sapere come mappare sulla coppia proprietà / valore . Se la risposta è "un ciclo", allora così sia, ma mi chiedo se esiste una soluzione più elegante.
Dan

Quello che hai nel tuo esempio non è un elenco di proprietà. Gli elenchi di proprietà hanno un numero pari di elementi, gli elementi dispari sono nomi di proprietà, gli elementi pari sono valori di proprietà. Quello che hai sarebbe, probabilmente, chiamato un albero (un elenco di elenchi).
wvxvw,

Risposte:


8

Probabilmente otterrai varie looprisposte di iterazione. AFAIK, non esiste un modo idiomatico per farlo. Non penso nemmeno che sia molto comune voler accumulare solo i valori delle proprietà (non associarli alle proprietà).

Ecco un modo semplice per farlo:

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

Per il caso più generale, in cui si desidera mappare una funzione binaria su un plist:

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

13

Ciò dipenderebbe probabilmente da una situazione. In generale, se avessi bisogno di legare un numero di valori con un numero di nomi, userei una tabella hash, ma se dovessi usare un elenco di proprietà, userei cl-loop. Di seguito sono riportati alcuni esempi:

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

E se hai una struttura di dati che mostri nel tuo esempio:

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

5

Per me la risposta è trasformare il plist in un elenco, che è una struttura di dati equivalente, solo la metà più profonda e più facile da manipolare.


3
Bene, è una specie di risposta (e un buon consiglio), ma più di un commento.
Estratto il

4

Con Emacs dall'attuale Git HEAD che diventerà Emacs 25, puoi fare quanto segue, cioè trasformare facilmente il plist in un alist con la nuova seq-partitionfunzione e quindi elaborare le voci di alist usando standard 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))

Dai un'occhiata ai seq-partitiondocumenti usando C-h f seq-partition RET.


1

Un'idea è quella di utilizzare -map-indexedda dashe applicare la trasformazione solo ai valori dispari della 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)

Un'altra idea è quella di convertire un plist in una tabella hash, usando ht<-plistda ht:

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

Si noti che hash-table non mantiene l'ordine degli oggetti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.