[N]BUTLAST perform a single pass over the list
authorPaul Khuong <pvk@pvk.ca>
Mon, 4 Nov 2013 18:55:10 +0000 (13:55 -0500)
committerPaul Khuong <pvk@pvk.ca>
Mon, 4 Nov 2013 18:58:10 +0000 (13:58 -0500)
The old code would first count the number of conses, and then
SUBSEQ/(RPLACD/NTHCDR).  Instead traverse the list with two offset
pointers that advance in lockstep.

Based on a patch by Johan Andersson, on lp#1245697.

NEWS
src/code/list.lisp

diff --git a/NEWS b/NEWS
index 9b52268..a53c8e1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@
 changes relative to sbcl-1.1.13:
   * optimization: complicated TYPEP tests are less opaque to the type
     propagation pass. (lp#1229340)
+  * optimization: [N]BUTLAST perform a single pass over the list. (lp#1245697)
   * enhancement: Top-level defmethod without defgeneric no longer causes
     undefined-function warnings in subsequent forms. (lp#503095)
   * bug fix: EQUALP now compares correctly structures with raw slots larger
index da6ca8e..fe663c6 100644 (file)
       ((atom 2nd) 3rd)
     (rplacd 2nd 3rd)))
 \f
-(flet (;; Return the number of conses at the head of the
-       ;; possibly-improper list LIST. (Or if LIST is circular, you
-       ;; lose.)
-       (count-conses (list)
-         (do ((in-list list (cdr in-list))
-              (result 0 (1+ result)))
-             ((atom in-list)
-              result)
-           (declare (type index result)))))
-  (declare (ftype (function (t) index) count-conses))
-  (defun butlast (list &optional (n 1))
-    (if (typep n 'index)
-        (let ((n-conses-in-list (count-conses list)))
-          (cond ((zerop n)
-                 ;; (We can't use SUBSEQ in this case because LIST isn't
-                 ;; necessarily a proper list, but SUBSEQ expects a
-                 ;; proper sequence. COPY-LIST isn't so fussy.)
-                 (copy-list list))
-                ((>= n n-conses-in-list)
-                 nil)
-                (t
-                 ;; (LIST isn't necessarily a proper list in this case
-                 ;; either, and technically SUBSEQ wants a proper
-                 ;; sequence, but no reasonable implementation of SUBSEQ
-                 ;; will actually walk down to the end of the list to
-                 ;; check, and since we're calling our own implementation
-                 ;; we know it's reasonable, so it's OK.)
-                 (subseq list 0 (- n-conses-in-list n)))))
-        nil))
-  (defun nbutlast (list &optional (n 1))
-    (cond ((zerop n)
-           list)
-          ((not (typep n 'index))
-           nil)
-          (t (let ((n-conses-in-list (count-conses list)))
-               (unless (<= n-conses-in-list n)
-                 (setf (cdr (nthcdr (- n-conses-in-list n 1) list))
-                       nil)
-                 list))))))
+(defun butlast (list &optional (n 1))
+  (cond ((zerop n)
+         (copy-list list))
+        ((not (typep n 'index))
+         nil)
+        (t
+         (let ((head (nthcdr (1- n) list)))
+           (and (consp head)      ; there are at least n
+                (collect ((copy)) ; conses; copy!
+                  (do ((trail list (cdr trail))
+                       (head head (cdr head)))
+                      ;; HEAD is n-1 conses ahead of TRAIL;
+                      ;; when HEAD is at the last cons, return
+                      ;; the data copied so far.
+                      ((atom (cdr head))
+                       (copy))
+                    (copy (car trail)))))))))
+
+(defun nbutlast (list &optional (n 1))
+  (cond ((zerop n)
+         list)
+        ((not (typep n 'index))
+         nil)
+        (t
+         (let ((head (nthcdr (1- n) list)))
+           (and (consp head)       ; there are more than n
+                (consp (cdr head)) ; conses.
+                ;; TRAIL trails by n cons to be able to
+                ;; cut the list at the cons just before.
+                (do ((trail list (cdr trail))
+                     (head (cdr head) (cdr head)))
+                    ((atom (cdr head))
+                     (setf (cdr trail) nil)
+                     list)))))))
 
 (defun ldiff (list object)
   "Return a new list, whose elements are those of LIST that appear before