Сопоставить функцию со списком свойств?

17

Вопрос: каков идиоматический способ сопоставления функции в списке свойств?

Различные функции отображения ( mapcarи семейство) отображают функцию на последовательность, такую ​​как список. Как можно использовать эти функции при работе со списком свойств , т. Е. При попытке отобразить каждое из свойств, содержащихся в списке (который будет любым другим элементом, начиная с первого)? Мне кажется, что функция отображения должна была бы получить доступ к списку в парах элементов, а не как отдельные элементы.

Например, как взять список свойств и собрать все значения свойств? Если бы это был список ассоциаций, это было бы довольно просто:

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

Я уверен, что это можно сделать с помощью цикла, но это кажется немного трудоемким, и мне интересно, есть ли более идиоматический способ сделать это.

Дэн
источник
Уточните, хотите ли вы отображать только значения свойств (как это звучит и как делает ваш mapcarпример alist) или вы хотите отобразить пары символов и значений свойств. Последнее, я думаю, является более общим (более полезным).
Дрю
@Drew: меня больше интересует общее дело; пример был самым простым, о котором я мог подумать. Я хотел бы знать, как отобразить пару свойство / значение . Если ответ «петля», пусть будет так, но мне интересно, есть ли более элегантное решение.
Дан
То, что у вас есть в вашем примере, не является списком свойств. Списки свойств имеют четное количество элементов, нечетные элементы являются именами свойств, четные элементы являются значениями свойств. То, что у вас есть, вероятно, называется деревом (списком списков).
wvxvw

Ответы:

8

Скорее всего, вы получите различные loopитерационные ответы. AFAIK, нет идиоматического способа сделать это. Я даже не думаю, что очень часто хочется накапливать только значения свойств (не связывая их со свойствами).

Вот один простой способ сделать это:

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

Для более общего случая, когда вы хотите отобразить бинарную функцию через 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

Это, вероятно, будет зависеть от ситуации. В общем, если мне нужно связать несколько значений с несколькими именами, я бы использовал хеш-таблицу, но если бы мне пришлось использовать список свойств, я бы использовал cl-loop. Ниже приведены некоторые примеры:

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

И если у вас есть структура данных, которую вы показываете в своем примере:

(cl-loop for (key value) in '((:prop1 a) (:prop2 b) (:prop3 c))
         collect value)
;;; (a b c)
wvxvw
источник
5

Для меня ответ заключается в том, чтобы превратить список в alist, который является эквивалентной структурой данных, вдвое менее глубоким и более простым в управлении.

Стефан
источник
3
Ну, это своего рода ответ (и хороший совет), но это скорее комментарий.
Дрю
4

С Emacs из текущей Git HEAD, которая станет Emacs 25, вы можете сделать следующее, то есть легко превратить plist в alist с помощью новой seq-partitionфункции, а затем обработать записи в alist с использованием стандарта 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))

Посмотрите на seq-partitionдокументы, используя C-h f seq-partition RET.

Тассило Хорн
источник
1

Одна идея состоит в том, чтобы использовать -map-indexedиз dashи применять преобразование только к нечетным значениям списка:

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

Другая идея состоит в том, чтобы просто преобразовать PLIST в хэш-таблицу, используя ht<-plistиз ht:

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

Обратите внимание, что хэш-таблица не сохраняет порядок элементов.

Миржан Иркегулов
источник