Является ли '(a. B) действительно списком?

15

Я действительно запутался с .обозначениями. Это '(a . b)список?

(listp '(a . b))возвращается, tно когда я хочу знать, его длина (length '(a . b))выдает ошибку Wrong type argument: listp, b. То же самое для других функций, как nth,mapcarи т.д., они все дают ту же ошибку

Есть ли какая-нибудь функция, которую я могу различить между '(a b)и '(a . b)?


Контекст: я столкнулся с этой проблемой, когда хотел реализовать рекурсивную версию mapcar. Вот моя реализация

(defun true-listp (object)
"Return non-`nil' if OBJECT is a true list."
(and (listp object)  (null (cdr (last object)))))

(defun recursive-mapcar (func list)
"Evaluates func on elements of the list, then on elements of elements  of the list and so forth." 
(let ((output nil))
(flet ((comp (a b) nil)
       (call-fun-and-save (x) (add-to-list 'output (funcall func x) t 'comp))
       (recursion (l)
                  (mapcar
                   (lambda (x)
                     (call-fun-and-save x)
                     (if (and (true-listp x))  ;; HERE I use true-listp, testing for list or cons is not sufficient
                         (recursion x)))
                    l)))
   (recursion list))
  output))

Я использую это, чтобы извлечь все определенные теги из разобранного HTML. Пример htmlдля разбора

;; buffer 'html'
<html>
<body>
<table style="width:100%">
  <tr>  <td>Jill</td>  <td>Smith</td>  <td>50</td> </tr>
  <tr>  <td>Eve</td>   <td>Jackson</td>   <td>94</td> </tr>
</table>
</body>
</html>

Тогда я извлекаю все <td>как

(with-current-buffer (get-buffer "html")
  (let ((data (libxml-parse-html-region (point-max) (point-min))))

    ;; gat only <td> tags
    (-non-nil
     (recursive-mapcar
      (lambda(x) (and (consp x) (equal 'td (car x)) x))
      data))
    data
    )
  )
Том
источник
1
В true-list-pElisp его нет просто потому, что не было найдено, что оно достаточно полезно для его предоставления. Действительно, я не могу вспомнить, когда в последний раз я хотел проверить правильность списка, поэтому, возможно, если вы дадите нам немного больше информации о вашем сценарии использования, мы поможем решить вашу проблему другим способом.
Стефан
@Stefan Короче говоря, я хочу реализовать рекурсивный mapcar, я оцениваю данную функцию по элементам данного списка, затем по элементам элементов списка, затем по элементам элементов списка и так далее. Поэтому мне нужно знать, является ли элемент истинным списком или нет.
Том
Это полезно, например, когда я анализирую html libxml-parse-html-regionи хочу извлечь все <td>теги.
Том
Можете ли вы показать нам конкретный пример, где вы можете получить либо правильный список, либо неправильный список, либо что-то еще, и где вам нужно по-разному обрабатывать 3 случая? В большинстве случаев, с которыми мне приходилось иметь дело, «правильные» и «неправильные» случаи могут передаваться до тех пор, пока мы не дойдем до фактического неправильного хвоста, поэтому вам снова не нужно проверять, правильно ли это или нет: просто проверьте, это conspвместо.
Стефан
1
libxml не просто возвращает списки списков. Каждый список, представляющий элемент XML, имеет форму (атрибуты символа. Содержимое). Таким образом, ваш код не должен применять mapcar рекурсивно ко всем элементам списков, а только к cddrсписку (чтобы пропустить имя элемента и атрибуты). Как только вы это сделаете, вы должны обнаружить, что все списки являются правильными, и ваша проблема исчезнет. Это также исправит ошибку в вашем коде, когда вы можете спутать tdатрибут для tdэлемента.
Стефан

Ответы:

22

Это удовлетворяет listp, так что в этом смысле это список. listpпросто проверяет, является ли что-то минусом или nil(иначе ()), с одной стороны, или чем-то еще, с другой стороны.

Правильный список или истинный список (или список , который не пунктирный список или циклический список) является то , что , listpа также имеет nilв своих последних кордах. То есть, список XSявляется надлежащим , если (cdr (last XS))есть nil(и то , как вы отличаете его).

Другой способ сделать это - правильный список имеет правильный список в качестве своего cdr . Именно так тип данных (правильный) List определяется на типизированных языках. Это определение универсального и рекурсивного типа: общая часть говорит, что первый аргумент для конструктора непустого списка (часто называемого consBTW) может быть любого типа. Рекурсивная часть говорит, что ее вторым аргументом является экземпляр типа (правильный) List .

Да, вы проверяете, является ли данный listpсписок правильным, используя (cdr (last XS))is nil. Чтобы проверить, является ли CDR критерия сам по себе правильным списком, вы должны продолжить проверять его CDR, до конца - последние минусы, чтобы увидеть, так ли это nil. Вы можете определить предикат для этого следующим образом, если хотите:

(defun true-listp (object)
  "Return non-`nil' if OBJECT is a true list."
  (and (listp object)  (null (cdr (last object)))))

Хотя циклический список не имеет конца, Emacs (начиная с Emacs 24) достаточно умен для lastправильной проверки , поэтому этот код работает даже для циклического списка (но только для Emacs 24.1 и новее; для более ранних версий вы получаете «бесконечную» рекурсию). до переполнения стека).

Вы можете использовать такие функции, как lengthтолько в надлежащих списках и других последовательностях. Смотрите также функцию safe-length.

См. Руководство по Elisp, узел Cons Cells .

Что касается нотации, (a b)это просто синтаксический сахар для (a . (b . nil))- см. Руководство по Elisp, нотация Dotted Pair Notation

Нарисовалась
источник
Какова наилучшая практика для проверки правильности списка? Проверка, (cdr (last XS))является ли nilэто вялым. Разве нет такой функции, как proper-list-p?
Том
@tom: Это, или что-то эквивалентное, необходимо - вы должны проверить последнюю ячейку минусов. Я добавил больше об этом в ответ сейчас.
Дрю
@Drew Я бы изменил тело функции на (unless (atom x) (not (cdr (last x))))Так что вы можете даже позвонить (true-list-p "text")и nilне получить ошибку.
Том
@tom: верно; Спасибо. На самом деле, это должно быть проверено в первую очередь, чтобы убедиться, что это минусы или nil(то есть listp). (Кроме того , FWIW, я не использую unlessили whenдля возвращаемого значения я использую. and, orИ ifдля этого.)
Дрю