Asso c функция в Common Lisp и 2 вопроса - PullRequest
0 голосов
/ 18 апреля 2020

У меня есть список, который содержит вхождения букв в предложении:

(setf mylist '((a 1) (b 2) (a 1) (b 1) (o 2) (m 1))) ; "abba boom"

Я хотел бы связать все пары, имеющие, например, букву b:

(assoc 'b mylist) ; => returns just the first occurance of b: (B 2)

Как получить все пары, связанные с b и перечислить их? например,

(my-assoc 'b mylist) ; => ((B 2) (B 1))

2- Как написать функцию, которая будет группировать буквы вместе с суммой их вхождений? например,

(my-group-sum mylist) ; => ((A 2) (B 3) (O 2) (M 1))

Вот мое взятие, предполагая, что my-assoc, как описано выше, существует:

(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)))); cdr should be "second" 
          collect (letter occurance)))

Примечание. Этот код не скомпилирован и не протестирован. Скорее всего, это будет ошибкой, даже если функция my-assoc была доступна. Это просто для демонстрации.

Ответы [ 2 ]

2 голосов
/ 19 апреля 2020

Чтобы завершить превосходный и подробный ответ @coredump, я хотел бы упомянуть другой (и более эффективный) подход к проблеме «группирования», представленной в вопросе.

Этот подход просто сканирует перечислите только один раз для выполнения операции, используя таблицу ha sh для сбора сумм:

CL-USER> (defun my-group-sum (lst)
           (let ((table (make-hash-table)))
             (loop for (letter num) in lst
                   do (incf (gethash letter table 0) num))
             (loop for key being the hash-key of table
                   using (hash-value val)
                   collect (list key val))))
MY-GROUP-SUM
CL-USER> (my-group-sum '((a 1) (b 2) (a 1) (b 1) (o 2) (m 1)))
((B 3) (M 1) (O 2) (A 2))

В первом l oop (gethash letter table 0), если letter не существует в таблице , создает для него запись со значением 0 или возвращает текущее значение letter, и incf увеличивает его, добавляя текущее число.

Второй l oop просто собирает результат. Конечно, если вам нужно что-то отсортировать, вам нужно добавить явный вызов к sort.

2 голосов
/ 19 апреля 2020

Давайте использовать тот же пример, я использую defvar для правильного объявления переменной:

(defvar *list* '((a 1) (b 2) (a 1) (b 1) (o 2) (m 1)))
  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)
    
...