Функция для объединения двух списков свойств?

11

Я не нашел стандартной библиотечной функции Elisp для объединения двух списков свойств, например:

(setq pl nil)
(setq pl (plist-put pl 'key-1 'value-1))
(setq pl (plist-put pl 'key-2 'value-2))

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

Обновления, основанные на комментариях :

  1. В ответ на «многогранный» комментарий:

Я полагаю, что такой функции нет, потому что есть разные (и, возможно, правильные) ответы на вопрос: что делать, если у вас есть дубликаты имен свойств с разными значениями?

Да, есть вопрос о том, как объединить дубликаты, но есть относительно немного способов решить эту проблему. Я вижу два общих подхода. Во-первых, порядок аргументов может разрешать дубликаты; например, самые правые победы, как при слиянии Clojure . Во-вторых, слияние может делегироваться пользовательской функции обратного вызова, как в слиянии Ruby .

В любом случае, тот факт, что существуют разные способы сделать это, не мешает многим другим языковым стандартным библиотекам предоставлять функцию слияния. Тот же самый общий аргумент можно сказать о сортировке, но Elisp предоставляет функции сортировки.

  1. "Не могли бы вы уточнить?" / «Пожалуйста, уточните, какое поведение вы ищете.»

Вообще говоря, я открыт для того, что использует сообщество Elisp. Если вы хотите конкретный пример, вот один пример, который будет работать:

(a-merge-function '(k1 1) '(k2 2 k3 3) '(k3 0))

И возвращает

'(k1 1 k2 2 k3 0))

Это был бы самый правый стиль, как слияние Clojure.

  1. "Это списки, так что просто добавить?"

Нет, appendне сохраняет семантику списка свойств . Эта:

(append '(k1 1 k2 2) '(k2 0))

Возвращает это:

(k1 1 k2 2 k2 0)

append - это встроенная функция в `C исходном коде '.

(добавить и отдохнуть ПОСЛЕДОВАТЕЛЬНОСТИ)

Объединить все аргументы и сделать результат списком. Результатом является список, элементы которого являются элементами всех аргументов. Каждый аргумент может быть списком, вектором или строкой. Последний аргумент не копируется, просто используется как хвост нового списка.

  1. «И ваш пример не показывает ничего похожего на слияние - он даже не показывает два списка свойств».

Да, это так; это делает слияние шаг за шагом. Это показывает, как выполнение слияния с использованием документированных функций списка свойств Elisp мучительно многословно:

(setq pl nil)
(setq pl (plist-put pl 'key-1 'value-1))
(setq pl (plist-put pl 'key-2 'value-2))

Просто отобразите полученное выходное значение из pl:

(key-1 value-1 key-2 value-2)

Повторюсь, я могу написать функцию для решения этой проблемы, но сначала я хотел выяснить, существует ли такая функция где-то в общем пользовании.

Наконец, если бы вы отклонили вопрос, потому что вы нашли его неясным, я бы попросил вас пересмотреть его сейчас, когда я приложил некоторые усилия, чтобы уточнить. Это не недостаток исследований. Документация Elisp по «Спискам» не отвечает на этот вопрос.

Дэвид Дж.
источник
2
Это списки, так просто append?
abo-abo
2
Пожалуйста, уточните, какое поведение вы ищете. Есть много способов «объединить» два списка. И ваш пример не показывает ничего похожего на слияние - он даже не показывает два списка свойств. Пока этот вопрос должен быть закрыт как неясный. FWIW, имейте в виду, что пара ближе к передней части plist затеняет любую пару, имеющую тот же ключ, который находится дальше от передней части. Таким образом, объединение может означать размещение элементов из одного списка перед элементами из другого и т. Д.
Дрю
1
@ abo-abo: Оказывается, что в Руководстве по Emacs Lisp прямо указано, что имена свойств должны быть разными .
Константин
3
Чтобы выиграть самый правый список, вам нужно просто изменить порядок списков, которые вы передаете append: (let ((args '((:a 1 :b 1) (:b 2) (:a 3)))) (apply #'append (reverse args))) => (:a 3 :b 2 :a 1 :b 1)это то же самое, что (:a 3 :b 2 :a 1)и при использовании только функций plist для доступа к plist.
tarsius
1
@Constantine: верно, хотя ни то, plist-getни другое, plist-memberпохоже, не волнует, есть ли несколько идентичных ключей. Похоже, они ведут себя аналогично спискам в этом отношении (plist-get '(:a "a" :b "b" :a "c") :a) ==> "a". Между тем (plist-put '(:a "a" :b "b" :a "c") :a "d")заменяет значение первого :aключа, а не второго.
Дан

Ответы:

8

Org-mode, включенный в Emacs, имеет функцию слияния plist:

(defun org-combine-plists (&rest plists)
  "Create a single property list from all plists in PLISTS.
The process starts by copying the first list, and then setting properties
from the other lists.  Settings in the last list are the most significant
ones and overrule settings in the other lists."
  (let ((rtn (copy-sequence (pop plists)))
        p v ls)
    (while plists
      (setq ls (pop plists))
      (while ls
        (setq p (pop ls) v (pop ls))
        (setq rtn (plist-put rtn p v))))
    rtn))

Чтобы использовать его, вам нужно (require 'org)сначала загрузить файл. К сожалению, это очень большой файл, 900 + КБ, поэтому он не очень полезен в качестве служебной библиотеки. Было бы неплохо иметь что-то похожее на стандартную упаковку.

Недавно я запустил очень маленький и понял, что списки и списки не обрабатываются одинаково по аргументам - например, (plist-get LIST KEY) против (assoc KEY LIST), что должно быть неким печальным пережитком оптимизации (или?) ,

Но да, Emacs действительно нуждается в хорошей библиотеке plist - я не сталкивался с одной в моих поисках, но все еще возможно, что где-то есть такая, иначе нам придется ее запустить и поместить в Elpa / Melpa. ,

Было бы неплохо иметь библиотеку alist с тем же интерфейсом.

Брайан Бернс
источник
6

Чтение руководства и просмотр списка из C-u C-h a plist RETне приводит ни к одной функции для объединения двух списков свойств. Расширения Common Lisp не предоставляют какой-либо функции специально для работы со списками свойств, только поддержка размещения ( getf/ setf/…). Так что вам нужно либо положиться на стороннюю библиотеку, либо свернуть свою.

Прокатить свой не так уж сложно. Эта реализация использует последнее значение в случае конфликта.

(defun plist-merge (&rest plists)
  (if plists
      (let ((result (copy-sequence (car plists))))
        (while (setq plists (cdr plists))
          (let ((plist (car plists)))
            (while plist
              (setq result (plist-put result (car plist) (car (cdr plist)))
                    plist (cdr (cdr plist))))))
        result)
    nil))

(plist-merge '(:x 2 :y 3)
             '(     :y 0 :z 7))
=>            (:x 2 :y 0 :z 7)
Жиль "ТАК - прекрати быть злым"
источник
отлично. Почему ты copy-sequenceпервый, а не остальные? А также, вы можете очистить гнездового немного с cadrи cddr.
Фоммил
на самом деле org-combine-plists(ниже) более или менее очищенная версия. Я до сих пор не понимаю, зачем им copy-sequenceмашина.
Фоммил
0

Я знаю, что на этот вопрос уже дан ответ, но если кому-то это интересно, я взял orgреализацию и немного поиграл в нее.

(defun plist-merge (&rest plists)
  "Create a single property list from all PLISTS.
Inspired by `org-combine-plists'."
  (let ((rtn (pop plists)))
    (dolist (plist plists rtn)
      (setq rtn (plist-put rtn
                           (pop plist)
                           (pop plist))))))
fommil
источник