Zamapować funkcję na liście właściwości?

17

P: Jaki jest idiomatyczny sposób mapowania funkcji na liście właściwości?

Różne funkcje mapowania ( mapcari rodzina) mapują funkcję na sekwencji, takiej jak lista. W jaki sposób korzysta się z tych funkcji, gdy ma się do czynienia z listą właściwości , tj. Przy próbie mapowania każdej z właściwości zawartych na liście (którym byłby każdy inny element, zaczynając od pierwszego)? Wydaje mi się, że funkcja mapowania musiałaby uzyskać dostęp do listy w parach elementów, a nie jako pojedynczych elementów.

Jako zabawkowy przykład, w jaki sposób wziąć listę właściwości i zebrać wszystkie wartości właściwości? Gdyby to była lista skojarzeń, byłoby to dość proste:

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

Jestem pewien, że można to zrobić za pomocą pętli, ale wydaje się to trochę pracochłonne i zastanawiam się, czy istnieje bardziej idiomatyczny sposób na zrobienie tego.

Dan
źródło
Wyjaśnij, czy chcesz odwzorować tylko wartości właściwości (to, jak to brzmi i tak właśnie mapcardziała Twój przykład), czy też chcesz odwzorować pary symboli właściwości i wartości właściwości. To drugie jest bardziej ogólne (bardziej ogólnie przydatne), tak sądzę.
Drew,
@Drew: Bardziej interesuje mnie ogólna sprawa; przykład był najprostszy, jaki mogłem wymyślić. Chciałbym wiedzieć, jak mapować parę właściwości / wartości . Jeśli odpowiedź brzmi „pętla”, niech tak będzie, ale zastanawiam się, czy istnieje bardziej eleganckie rozwiązanie.
Dan
To, co masz w tym przykładzie, nie jest listą nieruchomości. Listy właściwości mają parzystą liczbę elementów, nieparzyste elementy są nazwami właściwości, parzyste elementy są wartościami właściwości. To, co masz, byłoby prawdopodobnie nazwane drzewem (lista list).
wvxvw 11.04.15

Odpowiedzi:

8

Prawdopodobnie otrzymasz różne loopi iteracyjne odpowiedzi. AFAIK, nie ma idiomatycznego sposobu na zrobienie tego. Nawet nie sądzę, że bardzo często chce się kumulować tylko wartości właściwości (nie kojarząc ich z właściwościami).

Oto jeden prosty sposób:

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

W bardziej ogólnym przypadku, gdy chcesz zmapować funkcję binarną na 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))
Rysował
źródło
13

Prawdopodobnie zależy to od sytuacji. Ogólnie, gdybym musiał powiązać kilka wartości z wieloma nazwami, użyłbym tabeli skrótów, ale gdybym musiał użyć listy właściwości, użyłbym cl-loop. Poniżej kilka przykładów:

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

A jeśli masz strukturę danych pokazaną w przykładzie:

(cl-loop for (key value) in '((:prop1 a) (:prop2 b) (:prop3 c))
         collect value)
;;; (a b c)
wvxvw
źródło
5

Dla mnie odpowiedzią jest przekształcenie listy w alist, która jest równoważną strukturą danych, o połowę głębszą i łatwiejszą do manipulowania.

Stefan
źródło
3
Cóż, to rodzaj odpowiedzi (i dobrej porady), ale bardziej komentarz.
Drew,
4

Z Emacsem z bieżącej GIT HEAD, który stanie się Emacs 25, możesz wykonać następujące czynności, tj. Łatwo zamienić plist w alist dzięki nowej seq-partitionfunkcji, a następnie przetworzyć wpisy alist przy użyciu standardowego 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))

Spójrz na seq-partitiondokumenty za pomocą C-h f seq-partition RET.

Tassilo Horn
źródło
1

Jednym z pomysłów jest wykorzystanie -map-indexedz dashi zastosować transformację tylko nieparzystych wartości listy:

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

Innym pomysłem jest po prostu przekonwertować plist do hash-table, korzystając ht<-plistz ht:

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

Zauważ, że tablica skrótów nie zachowuje kolejności elementów.

Mirzhan Irkegulov
źródło