Как удалить вложенные скобки в LISP

Как я могу рекурсивно удалить вложенные скобки в Common LISP, например

  (unnest '(a b c (d e) ((f) g))) => (a b c d e f g)
  (unnest '(a b))                 => (a b)
  (unnest '(() ((((a)))) ()))     => (a)

Спасибо


person bubdada    schedule 21.04.2010    source источник
comment
Вы не удаляете скобки. Скобки — это всего лишь часть печатного представления списков. То, что вы делаете, это выравнивание списков.   -  person Svante    schedule 22.04.2010


Ответы (10)


Вот что я бы сделал:

(ql:quickload "alexandria")
(alexandria:flatten list)

Это работает главным образом потому, что у меня уже установлен Quicklisp.

person Xach    schedule 01.11.2010
comment
Так скромно! Это работает главным образом потому, что вы уже создали Quicklisp. - person Joe Taylor; 31.07.2012

Я понимаю, что это старая тема, но она одна из первых, которая появляется, когда я гуглю lisp flatten. Решение, которое я обнаружил, похоже на те, что обсуждались выше, но форматирование немного отличается. Я объясню это так, как будто вы новичок в lisp, как и я, когда впервые загуглил этот вопрос, так что вполне вероятно, что и другие тоже.

(defun flatten (L)
"Converts a list to single level."
    (if (null L)
        nil
        (if (atom (first L))
            (cons (first L) (flatten (rest L)))
            (append (flatten (first L)) (flatten (rest L))))))

Для новичков в lisp, это краткое изложение.

В следующей строке объявляется функция flatten с аргументом L.

(defun flatten (L)

Строка ниже проверяет наличие пустого списка.

    (if (null L)

Следующая строка возвращает nil, потому что cons ATOM nil объявляет список с одной записью (ATOM). Это базовый случай рекурсии, который сообщает функции, когда следует остановиться. Строка после этого проверяет, является ли первый элемент в списке атомом, а не другим списком.

        (if (atom (first L))

Затем, если это так, он использует рекурсию для создания сглаженного списка этого атома, объединенного с остальной частью сглаженного списка, который сгенерирует функция. cons объединяет атом с другим списком.

            (cons (first L) (flatten (rest L)))

Если это не атом, то мы должны сгладить его, потому что это другой список, внутри которого могут быть дополнительные списки.

            (append (flatten (first L)) (flatten (rest L))))))

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

person Community    schedule 14.11.2013
comment
и это хвостовая рекурсия? - person ; 29.09.2017
comment
Это не хвостовая рекурсия, и, кроме того, вопрос не имеет значения: спецификация CL не требует исключения хвостовых вызовов, поэтому на нее нельзя полагаться. Это взорвется для длинных списков, а также для глубоких списков. Решение из другого ответа с использованием цикла взорвется только для глубоких списков. - person Dan Robertson; 18.04.2020

Вы можете определить это, например, так:

(defun unnest (x)
  (labels ((rec (x acc)
    (cond ((null x) acc)
      ((atom x) (cons x acc))
      (t (rec (car x) (rec (cdr x) acc))))))
    (rec x nil)))
person Jakob    schedule 21.04.2010

В Лиспе есть функция remove для удаления вещей. Здесь я использую версию REMOVE-IF, которая удаляет все элементы, для которых предикат истинен. Я проверяю, является ли это скобкой, и удаляю ее, если она верна.

Если вы хотите удалить круглые скобки, см. эту функцию:

(defun unnest (thing)
  (read-from-string
   (concatenate
    'string
    "("
    (remove-if (lambda (c)
                 (member c '(#\( #\))))
               (princ-to-string thing))
    ")")))

Обратите внимание, однако, как упоминает Сванте, обычно не «удаляют» круглые скобки.

person Rainer Joswig    schedule 22.04.2010

Просто оставив это здесь, поскольку я посетил этот вопрос с необходимостью выравнивания только одного уровня, а позже выяснил для себя, что (apply 'concatenate 'list ((1 2) (3 4) (5 6 7))) является более чистым решением в этом случае.

person Isti115    schedule 10.04.2020

В большинстве ответов уже упоминалось рекурсивное решение проблемы Flatten. Используя множественную диспетчеризацию Common Lisp Object System, вы можете рекурсивно решить проблему, определив 3 метода для 3 возможных сценариев:

(defmethod flatten ((tree null))
  "Tree is empty list."
  ())
(defmethod flatten ((tree list))
  "Tree is a list."
  (append (flatten (car tree))
          (flatten (cdr tree))))
(defmethod flatten (tree)
  "Tree is something else (atom?)."
  (list tree))

(flatten '(2 ((8) 2 (9 (d (s (((((a))))))))))) ; => (2 8 2 9 D S A)
person Student    schedule 08.04.2020

Это подход, основанный на накоплении. Локальная функция %flatten хранит накопитель хвоста (правая часть списка, которая уже была сплющена). Когда оставшаяся часть для выравнивания (левая часть списка) пуста, возвращается хвост. Когда часть, подлежащая выравниванию, не является списком, она возвращает эту часть с префиксом в конце. Когда часть, подлежащая выравниванию, представляет собой список, он выравнивает остальную часть списка (с текущим хвостом), а затем использует этот результат в качестве хвоста для выравнивания первой части списка.

(defun flatten (list)
  (labels ((%flatten (list tail)
             (cond
               ((null list) tail)
               ((atom list) (list* list tail))
               (t (%flatten (first list)
                            (%flatten (rest list)
                                      tail))))))
    (%flatten list '())))

CL-USER> (flatten '((1 2) (3 4) ((5) 6) 7))
(1 2 3 4 5 6 7)
person Joshua Taylor    schedule 21.10.2015

Я знаю, что этот вопрос действительно старый, но я заметил, что никто не использовал идиому push/nreverse, поэтому я загружаю это здесь.

функция reverse-atomize извлекает каждый "атом" и помещает его в output следующего вызова. В конце он создает сплющенный список в обратном порядке, который разрешается с помощью функции nreverse в функции atomize.

(defun reverse-atomize (tree output)
    "Auxillary function for atomize"
    (if (null tree)
      output
      (if (atom (car tree))
          (reverse-atomize (cdr tree) (push (car tree) output))
          (reverse-atomize (cdr tree) (nconc (reverse-atomize (car tree)
                                                               nil)
                                              output)))))

(defun atomize (tree)
    "Flattens a list into only the atoms in it"
     (nreverse (reverse-atomize tree nil)))

Итак, вызов atomize '((a b) (c) d) выглядит так:

(A B C D)

И если бы вы вызвали reverse-atomize с reverse-atomize '((a b) (c) d), это произошло бы:

(D C B A)

Людям нравится использовать такие функции, как push, nreverse и nconc, потому что они используют меньше оперативной памяти, чем их соответствующие функции cons, reverse и append. При этом двойная рекурсивная природа reverse-atomize имеет свои собственные ОЗУ.

person Spenser Truex    schedule 15.02.2016

Этот популярный вопрос имеет только рекурсивные решения (не считая ответа Райнера).

Давайте иметь циклическую версию:

(defun flatten (tree &aux todo flat)
  (check-type tree list)
  (loop
     (shiftf todo tree nil)
     (unless todo (return flat))
     (dolist (elt todo)
       (if (listp elt)
           (dolist (e elt)
             (push e tree))
           (push elt flat))))))
person coredump    schedule 08.04.2020

person    schedule
comment
Поскольку все списки, которые вы добавляете, исходят из вызова list в строке 3, вы можете использовать nconcing вместо appending. - person Dan Robertson; 18.04.2020