Удалить все вхождения elem с любого уровня списка в Common-lisp

Я хочу решить эту проблему, используя mapcar/lambdas. Я знаю, как делать это регулярно. Пока у меня есть что-то вроде:

(defun removal (lista elem &optional final)
    (cond
       ((and (atom lista) (eql lista elem))  nil)
       ((listp lista) (mapcan (lambda (e) ( removal e elem final)) lista))
       (t (nconc final lista))))

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

Все еще не работает должным образом даже после добавления лямбда и mapcan, он вообще не будет строить список


person Mocktheduck    schedule 17.12.2015    source источник
comment
Краткое введение в стиль и форматирование: people.ace.ed.ac.uk/staff/medward2/class/moz/cm/doc/contrib/   -  person coredump    schedule 17.12.2015
comment
Вы упоминаете mapcar/lambda, но также можете найти mapcan или mapcon.   -  person Joshua Taylor    schedule 17.12.2015


Ответы (3)


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

(defun remove-from-tree(el tree)
  (cond ((null tree) nil)
        ((consp (car tree))
         (cons (remove-from-tree el (car tree))
               (remove-from-tree el (cdr tree))))
        ((eql (car tree) el) (remove-from-tree el (cdr tree)))
        (t (cons (car tree) (remove-from-tree el (cdr tree))))))

В этом решении есть четыре возможных случая в cond:

  1. если дерево пусто, то вернуть пустое дерево,
  2. если первый элемент дерева является минусом, то есть деревом, рекурсивно примените функцию как к машине, так и к cdr исходного дерева, и считайте результаты, чтобы получить новое дерево,
  3. (к этому моменту мы знаем, что car дерева является атомом), если car равен eql элементу, то игнорируем car и продолжаем рекурсивно применять функцию к cdr,
  4. в противном случае построить новое дерево с машиной (которая является атомом, отличным от элемента) и результатом применения функции к cdr дерева.

Вот вторая версия:

(defun remove-from-tree(el tree)
  (mapcan (lambda(subtree)
            (cond ((null subtree) (list nil))
                  ((consp subtree) (list (remove-from-tree el subtree)))
                  ((eql subtree el) nil)
                  (t (list subtree))))
          tree))

В данном случае используется функционал mapcan, а так как это nconc все результаты, мы должны добавить list к нескольким ветвям cond внутренней функции.

Внутренняя функция, применяемая к «верхним» элементам дерева, имеет четыре случая:

  1. если поддерево равно null, возвращается значение (list nil), так что mapcan может объединить этот результат с другими,
  2. если поддерево является минусом, то есть деревом, рекурсивно вызвать функцию на нем и вернуть список результата,
  3. (на данный момент мы знаем, что поддерево является атомом), если оно равно удаляемому элементу, вернуть nil (и это не появится в результате mapcan),
  4. наконец, возвращает атом, отличный от элемента, в виде списка.
person Renzo    schedule 17.12.2015

Некоторые замечания:

  • когда вы имеете дело с несколькими уровнями списков, вы обычно работаете с деревом, не так ли? Я бы заменил removal и lista на tree-remove и tree.

  • что если lista это атом, а не число? вам лучше проверить, является ли lista числом, прежде чем использовать =, или, возможно, принять аргумент :test.

  • Вы говорите, что по какой-то причине он даже не работает. У вас нет сообщения об ошибке? что он говорит? что-то в undefined function: lista? вы используете символ lista в первой позиции списка в обычном контексте оценки.

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

    (defun tree-occurences (tree number)
      (let ((counter 0))
        (labels ((count-occurences (form)
                   (typecase form
                     (sequence (map nil #'count-occurences form))
                     (number (when (= form number) (incf counter))))))
          (count-occurences tree))
          counter))
    
person coredump    schedule 17.12.2015
comment
Если это атом, а не элемент, я думал, что он войдет в истинное состояние, но я не знаю, что он там должен делать. Да, в mapcar есть сообщение об ошибке, оно говорит, что List не является функцией. я не умею пользоваться ярлыками - person Mocktheduck; 17.12.2015
comment
Я имею в виду... Я не знаю ярлыков, поэтому я не должен их использовать. Я хочу как-то решить это, используя функции карты/лямбда - person Mocktheduck; 17.12.2015
comment
@ Melye77 Melye77 Дело в том, что (атом x) точно означает (не (consp x)), и поэтому число является атомом. Но многие другие вещи тоже являются атомами (например, строки). А поскольку функция = определена только для чисел (пожалуйста, прочтите спецификацию , это не так сложно), у вас будет ошибка для нечисловых атомов. Я добавил пример для labels. - person coredump; 17.12.2015
comment
Ах, хорошо, мне нужно использовать eql вместо =. Потому что я хочу, чтобы он работал и с символами - person Mocktheduck; 17.12.2015
comment
@ Melye77 Melye77 Если у вас нет прав на использование labels, просто определите вспомогательную функцию, например rec-removal или removal-helper. Да, вы должны использовать eql. - person coredump; 17.12.2015
comment
Я действительно хочу заставить его работать, используя функции карты. Я не понимаю, зачем мне нужна дополнительная функция. Я не понимаю, почему мой выдает ошибку во втором условии, когда я снова вызываю удаление. - person Mocktheduck; 17.12.2015
comment
Кроме того, я использую вспомогательную переменную counter и incf здесь выше, но это, вероятно, не то, что вы должны делать. Сохраняйте его функциональным и добавляйте параметры к своим функциям в своей работе. Если вам нужно вызвать mapcar с аргументами, оберните вызов лямбда: (mapcar (lambda (e) (recurse e final)) list), хотя я думаю, вам может понадобиться reduce здесь. - person coredump; 17.12.2015
comment
Давайте продолжим это обсуждение в чате. - person coredump; 17.12.2015

Вы упоминаете использование mapcar и лямбда, но Common Lisp также включает mapcan здесь может быть полезнее. Функция mapcan похожа на mapcar, но вместо создания списка результатов функции она берет результаты функции и объединяет их в список. Например.,

(mapcan (lambda (x)
          (list x x))
        '(1 2 3))
;=> (1 1 2 2 3 3)

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

(mapcan (lambda (x)
          (if (evenp x)
              (list x)                  ; if even, return (x)
              '()))                     ; if odd, return ()
        '(1 2 3 4 5 6))
;;=> (2 4 6)

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

(defun remove-all (element tree &key (test #'eql))
  (labels ((%remove-all (element tree)
             (cond
               ((funcall test element tree) '())
               ((atom tree) (list tree))
               (t (list (mapcan (lambda (child)
                                  (%remove-all element child))
                                tree))))))
    (car (%remove-all element tree))))
(remove-all 3 '(1 2 3 (3 2 1 (1 3 2))))
;;=> (1 2 (2 1 (1 2)))

(remove-all 3 5)
;;=> 5

(remove-all 3 3)
;;=> NIL

(remove-all 3 '(3))
;;=> NIL
person Joshua Taylor    schedule 17.12.2015