Давайте использовать тот же пример, я использую defvar
для правильного объявления переменной:
(defvar *list* '((a 1) (b 2) (a 1) (b 1) (o 2) (m 1)))
- Как получить все пары, связанные с b, и перечислить их?
Common Lisp определяет REMOVE
, который создает новый список с удалением некоторых элементов. Иногда вам нужно наоборот, функция, которая только сохраняет определенных элементов. Для этого вам нужно воспользоваться функцией дополнения. Например:
(remove 'a *list* :test-not #'eq :key #'car)
=> ((A 1) (A 1))
Вышеуказанное означает, что мы удаляем элементы x
, так что (eq 'a x)
равен false из-за аргумента :test-not
. Аргумент :key
говорит, что мы сравниваем записи по их первым элементам.
Вы можете свернуть свои собственные с помощью al oop:
(loop
for entry in *list*
when (eq (car entry) 'a)
collect entry)
Как написать функцию, которая будет группировать буквы вместе с суммой их вхождений?
Вы предоставили некоторую попытку, здесь она отформатирована:
(defun my-group-sum (lst)
(loop
for (letter num) in lst
do (let ((temp (my-assoc letter lst)) (occurance 0))
(dolist (pair temp)
(incf occurance (cdr pair))))
collect (letter occurance)))
Некоторые вещи не очень хороши, и если вы тестируете этот код в реальной среде, у вас должны быть ошибки либо при компиляции функции (если ваш Lisp компилирует код), либо при запуске кода в тесте. Давайте рассмотрим некоторые проблемы:
occurance
пишется occurrence
(небольшая проблема, но это помогает проверить) (letter occurance)
не так, как вы список построения, вы должны вызвать (list letter occurance)
, иначе это означает: вызов функции letter
с аргументом occurance
, даже если здесь не определена такая функция letter
(вероятно), и потому что вы хотите чтобы вернуть список из двух элементов.
при попытке построить (list letter occurance)
символ occurance
не ограничен в лексической области видимости. Он был связан внутри let
в выражении do
l oop, но здесь вы используете его за пределами этой области. Лучше позвонить collect
напрямую:
Вот переработанная версия:
(defun my-group-sum (lst)
(loop
for (letter num) in lst
collect (let ((temp (remove letter lst :test-not #'eql :key #'car))
(occurance 0))
(dolist (pair temp)
(incf occurance (cdr pair)))
(list letter occurance))))
Последняя форма в let
возвращает полученный результат.
Теперь, если вы протестируете свой код, вы увидите, что есть проблема: lst
не изменяется при вызове remove
(он создает список fre sh), что означает, что вы может найти другие совпадения в основном l oop. Например, сначала у вас есть:
((a 1) (b 1) (a 1))
Первая итерация l oop собирает (a 2)
, но затем оставшаяся итерация выполняется на ((b 1) (a 1))
, который все еще содержит a
,
Альтернативой может быть изменение привязки lst
или изменение списка. Я не уверен, что все реализации будут хорошо реагировать, если вы измените список, по которому вы повторяете в loop
, и изменение в стандарте запрещено согласно 3.6 Правила обхода и побочные эффекты .
Обычный способ итеративного изменения значения:
(loop for var = <init> then <next>)
... где далее следует when
- следующий список для использования. Вы можете адаптировать свой алгоритм так, чтобы он возвращал список, из которого вы удаляете элемент.
Но первым подходом для этого было бы разделить и победить проблему:
- написать функция
aggregate-step
, которая принимает список и возвращает два значения в списке: (1) накопленную запись, которая является либо nil
, либо формой (name count)
и (2) следующий список для использования. написать точку фиксации l oop, которая ее вызывает. Предполагая, что вы используете (list entry rest)
для возврата двух значений, и что entry
может быть nil
, вот как выглядит l oop:
(loop
for curlist = lst then rest
for (entry rest) = (aggregate-step curlist)
while entry
collect entry)