Ist '(a. B) wirklich eine Liste?

15

Ich bin wirklich verwirrt mit der .Notation. Ist '(a . b)eine Liste?

(listp '(a . b))kehrt taber zurück wenn ich wissen will wie lang es ist (length '(a . b))gibt es einen fehler Wrong type argument: listp, b. Das Gleiche gilt für andere Funktionen wie nth,mapcarusw. Sie geben alle den gleichen Fehler aus

Gibt es eine Funktion, die ich zwischen '(a b)und unterscheiden kann '(a . b)?


Kontext: Ich bin auf dieses Problem gestoßen, als ich die rekursive Version von implementieren wollte mapcar. Hier ist meine Implementierung

(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))

Ich benutze dieses, um alle spezifischen Umbauten von analysiertem HTML zu extrahieren. Beispiel für htmldas Parsen

;; 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>

Dann extrahiere ich alles <td>als

(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
    )
  )
tom
quelle
1
Es gibt keine true-list-pin Elisp, nur weil es nicht nützlich genug ist, um es bereitzustellen. In der Tat kann ich mich nicht erinnern, wann ich das letzte Mal testen wollte, ob eine Liste korrekt war. Wenn Sie uns also ein bisschen mehr Informationen zu Ihrem Anwendungsfall geben, können wir Ihnen helfen, Ihr Problem auf andere Weise zu lösen.
Stefan
@Stefan Kurz gesagt, ich möchte ein rekursives Mapcar implementieren. Ich bewerte eine gegebene Funktion für Elemente einer gegebenen Liste, dann für Elemente von Elementen der Liste, dann für Elemente von Elementen der Liste und so weiter. Ich muss also wissen, ob ein Element eine echte Liste ist oder nicht.
Tom
Es ist zum Beispiel nützlich, wenn ich HTML mit parse libxml-parse-html-regionund alle <td>Tags extrahieren möchte .
Tom
Können Sie uns ein konkretes Beispiel zeigen, wo Sie möglicherweise eine richtige oder eine falsche Liste oder etwas anderes erhalten und wo Sie die drei Fälle unterschiedlich behandeln müssen? In den meisten Fällen, mit denen ich mich befassen musste, können die "richtigen" und "falschen" Fälle geteilt werden, bis wir zum tatsächlichen falschen Schwanz gelangen es ist conspstattdessen.
Stefan
1
libxml gibt nicht nur Listen von Listen zurück. Jede Liste, die ein XML-Element darstellt, hat die Form (Symbolattribute. Inhalt). Daher sollte Ihr Code mapcar nicht rekursiv auf alle Elemente der Listen anwenden, sondern nur auf die cddrder Liste (um den Elementnamen und die Attribute zu überspringen). Sobald Sie das tun, sollten Sie feststellen, dass alle Listen korrekt sind und Ihr Problem verschwindet. Es behebt auch einen Fehler in Ihrem Code, durch den Sie ein tdAttribut für ein tdElement verwechseln können .
Stefan

Antworten:

22

Es erfüllt listp, also in diesem Sinne ist es eine Liste. listptestet nur, ob etwas ein Nachteil ist oder nil(aka ()) einerseits oder etwas anderes andererseits.

Eine richtige Liste oder eine echte Liste (oder eine Liste, die keine gepunktete Liste oder eine zirkuläre Liste ist) ist etwas, das listpauch nilals letzte CDR vorhanden ist. Das heißt, eine Liste XSist die richtige , wenn (cdr (last XS))ist nil(und das ist , wie man es unterscheiden).

Ein anderer Weg, dies zu formulieren, ist, dass eine richtige Liste eine richtige Liste als ihre cdr hat . Dies ist die Art und Weise der Datentyp (richtige) Liste wird in typisierten Sprachen definiert. Es handelt sich um eine generische und rekursive Typdefinition: Der generische Teil besagt, dass das erste Argument für den nicht leeren Listenkonstruktor (oft consBTW genannt) von einem beliebigen Typ sein kann. Der rekursive Teil besagt, dass sein zweites Argument eine Instanz des Typs (richtige) Liste ist .

Ja, Sie überprüfen mit listpis , ob eine gegebene Liste richtig (cdr (last XS))ist nil. Um zu überprüfen, ob die cdr des Lebewesens selbst eine richtige Liste ist, müssen Sie die cdr bis zum Ende überprüfen - die letzten Nachteile, um festzustellen, ob dies der Fall ist nil. Sie könnten ein Prädikat dafür wie folgt definieren, wenn Sie möchten:

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

Obwohl eine Zirkelliste kein Ende hat, ist Emacs (beginnend mit Emacs 24) intelligent genug, um eine lastkorrekte Überprüfung durchzuführen. Daher funktioniert dieser Code auch für eine Zirkelliste (jedoch nur für Emacs 24.1 und höher; für frühere Versionen erhalten Sie eine "unendliche" Rekursion bis zum Stapelüberlauf).

Sie können Funktionen beispielsweise lengthnur für ordnungsgemäße Listen und andere Sequenzen verwenden. Siehe auch Funktion safe-length.

Siehe das Elisp-Handbuch, Knoten Cons Cells .

Was die Notation betrifft , (a b)ist dies nur syntaktischer Zucker für (a . (b . nil))- siehe das Elisp-Handbuch, Knoten- Notation für gepunktete Paare

Drew
quelle
Was ist die beste Methode, um die richtige Liste zu überprüfen? Wenn die Überprüfung (cdr (last XS))ist nilist crumblesome. Gibt es nicht eine Funktion wie proper-list-p?
Tom
@tom: Das oder etwas Äquivalentes ist notwendig - Sie müssen die letzte Konsistenzelle überprüfen. Ich habe jetzt in der Antwort mehr darüber hinzugefügt.
Drew
@Drew Ich würde den Body der Funktion auf ändern, damit (unless (atom x) (not (cdr (last x))))Du selbst anrufen (true-list-p "text")und nilkeinen Fehler bekommen kannst .
Tom
@tom: Richtig; Danke. Eigentlich sollte es zuerst getestet werden, um sicherzustellen, dass es ein Nachteil ist oder nil(dh listp). (Auch FWIW, ich nicht verwenden unlessoder whenfür deren Rückgabewert ich verwende. and, orUnd iffür das.)
Drew