расширенный цикл с макросами LISP Loop - PullRequest
0 голосов
/ 01 июня 2018

Скажем, у вас есть список, составленный из списков.Например, список A: (list '(1 2 3) '(1 4 3) ).Далее вам предоставляется список B: '(0 2 3).Задача состоит в том, чтобы: определить, какой подсписок A соответствует B больше всего.Обратите внимание, что совпадение означает одинаковые целые числа в одинаковых позициях списка.Поэтому для этого случая ответом является подсписок '(1 2 3 ).Как вы можете автоматизировать это с помощью макроса цикла LISP?Ниже моя попытка.

(defun select-most-specific-list (listA listB)
  (loop with candidate_sublist = '()
        for sublist in  listA
        do (loop for number1 in sublist
                 for number2 in listB
                 when (= number1 number2)
                 do (setq candidate_sublist sublist)
                 finally (return candidate_list))))

Я даю следующий ввод:

(select-most-specific-list (list '(1 2 3) '(1 4 3) ) '(0 2 3))

Я получаю NIL.Кроме того, я почти уверен, что моя логика неверна.С учетом вышеприведенного ввода я ожидал, что он даст '(1 4 3) вместо правильного ответа '(1 2 3).Это связано с тем, что более тщательное изучение моей логики покажет, что я не храню результаты всех сравнений.Поэтому последнее успешное сравнение ошибочно диктует самый конкретный подсписок.Как мне этого добиться?

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

Проблемы

(loop for number1 in sublist
      for number2 in listB
      when (= number1 number2)
      do (setq candidate_sublist sublist)
      finally (return candidate_list))

Как только в вашем списке совпадут два числа, вы заменяете candidate_sublist, даже если оно хуже, чем предыдущая привязка.Предположим, что candidate_sublist равно (0 2 3), что соответствует списку ввода (вы не можете быть более похожим, чем этот).Затем вы перебираете следующего кандидата (0 9 9).С вашим кодом, начиная с (= 0 0), вы меняете candidate_sublist.Вы действительно должны проверить все значения в обоих списках, которые сравниваются, прежде чем принимать решение.

Функция расстояния

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

(defun distance (a b)
  (count nil (mapcar #'= a b)))

Или с циклом:

(defun distance (a b)
  (loop
     for aa in a
     for bb in b
     count (/= aa bb)))

Таким образом, чем больше различие между двумя списками, тем больше расстояние.

Частичные заказы

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

(0 1 2)

Оба (1 1 2) и (0 1 1) имеют одинаковое количество совпадающих значений.

Вы не можете просто вернуть одно best ответ, иначе вы выберете один из них на основе произвольного критерия (например, подробности реализации, такие как порядок обхода ваших списков).Что я хотел бы сделать, так это вычислить все списки, которые находятся на равном расстоянии от списка ввода.

(defun closest-lists (list candidates)
  (loop
     for candidate in candidates
     for distance = (distance list candidate)
     for better = T then (< distance min-distance)
     for min-distance = (if better distance min-distance)
     for best-matches = (cond
                          (better (list candidate))
                          ((= distance min-distance) (cons candidate best-matches))
                          (t best-matches))
     finally (return (values best-matches min-distance))))

Обобщение

Как сказано в комментариях @ Gwang-Jin Kim, closest-listsФункция может даже использоваться с другими функциями расстояния, если мы добавим ее в качестве параметра.Следуя соглашению о присвоении имен из sort, мы могли бы определить аргумент predicate, чтобы указать функцию сравнения, и аргумент key, чтобы указать, как получить сравниваемые значения (счет).Тогда наша функция на самом деле больше не связана со списками и может быть переименована, чтобы быть более общей:

(defun filter-by-score (candidates predicate &key (key #'identity))
  "Keep elements from CANDIDATES having the same best rank according to PREDICATE.

PREDICATE should return non-NIL if its first argument precedes its
second one. Elements are compared according the value returned by
applying KEY. The KEY function is guaranteed to be applied once only
for each element in CANDIDATES."
  (loop
     for candidate in candidates
     for score = (funcall key candidate)
     for better = T then (funcall predicate score best-score)
     for best-score = (if better score best-score)
     for best-items = (cond
                          (better (list candidate))
                          ((funcall predicate best-score score) best-items)
                          (t (cons candidate best-items)))
     finally (return (values best-items best-score))))

Тогда наша предыдущая функция может быть выражена как:

(filter-by-score candidates #'< :key (lambda (u) (distance list u)))

Номы также можем сделать:

CL-USER> (filter-by-score '("a" "ab" "cd" "ed" "fe" "aaa" "bbb" "nnn") 
                          #'> :key #'length)
("nnn" "bbb" "aaa")
3

Или даже:

CL-USER> (import 'alexandria:curry)
CL-USER> (ql:quickload :levenshtein)
CL-USER> (filter-by-score '("boat" "baobab" "brain" "biscuit")
                          #'<
                          :key (curry #'levenshtein:distance "ball"))
("brain" "boat")
3
0 голосов
/ 01 июня 2018

Я разбил его на:

(defun similarity (list1 list2)
   (loop for number1 in list1                             
         for number2 in list2                             ;thanks @Rainer Joswig!
         count (= number1 number2)))                      ;and @jkiiski!

(defun most-similar-list (lists qry-list &key (dist-func #'similarity))
  (let* ((simils        (loop for l in lists               ;thanks @coredump!
                              collect (funcall dist-func l qry-list)))
         (max-simil     (reduce #'max simils))              ;thanks @Rainer Joswig!
         (idx-max-simil (position max-simil simils :test #'=)))
    (elt lists idx-max-simil)))

Ваш пример

(most-similar-list (list '(1 2 3) '(1 4 3) ) '(0 2 3))
;; (1 2 3)

Приложение

Как неопределить distance:

;; (defun distance (list1 list2)
;;   (apply '+ (loop for number1 in list1    ;reduce is better! by @Rainer Joswig
;;                   for number2 in list2    ;because of the limit of arg numbers
;;                   collect (if (= number1 number2) 1 0)))) ;for apply (max ~50
;; - the exact number is implementation-dependent - see comments by him)

Более общий (собрать все минимальные расстояния / наиболее похожие элементы - как предложено @coredump)

Использование функции из Положение всех соответствующих элементов в списке , которое я изменил для переменных тестов:

(defun all-positions (qry-el l &key (test #'=))
  (loop for el in l
        and pos from 0
        when (funcall test el qry-el)
          collect pos))

Тогда решение:

(defun select-most-similar (lists qry-list &key (dist-func #'distance))
  (let* ((dists        (loop for l in lists               ;thanks @coredump!
                             collect (funcall dist-func l qry-list)))
         (max-dist     (reduce #'max dists))              ;thanks @Rainer Joswig!
         (max-dist-idxs (all-positions max-dist dists :test #'=)))
    (loop for i in max-dist-idxs
          collect (nth i lists))))

Или взяв функцию @ coredump и обобщив (Iиспользуется не мин, а макс):

(defun similarity (l1 l2)
  (loop for e1 in l1
        for e2 in l2
        count (= e1 e2)))

(defun most-specific-lists (lists one-list &key (dist-func #'similarity))
  (loop for l in lists
        for dist = (funcall dist-func l one-list)
        for max-dist = dist then (max dist max-dist)
        for max-dist-l = (list l) then 
          (cond ((= dist max-dist) (cons l max-dist-l))
                ((> dist max-dist) (list l))
                (t max-dist-l))
        finally (return (values max-dist-l max-dist))))
...