tsplit-sequence.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://hg6vgqziawt5s4dj.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) LICENSE
       ---
       tsplit-sequence.lisp (8164B)
       ---
            1 ;;;; -*- Mode: Lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; SPLIT-SEQUENCE
            4 ;;;
            5 ;;; This code was based on Arthur Lemmens' in
            6 ;;; <URL:http://groups.google.com/groups?as_umsgid=39F36F1A.B8F19D20%40simplex.nl>;
            7 ;;;
            8 ;;; changes include:
            9 ;;;
           10 ;;; * altering the behaviour of the :from-end keyword argument to
           11 ;;; return the subsequences in original order, for consistency with
           12 ;;; CL:REMOVE, CL:SUBSTITUTE et al. (:from-end being non-NIL only
           13 ;;; affects the answer if :count is less than the number of
           14 ;;; subsequences, by analogy with the above-referenced functions).
           15 ;;;
           16 ;;; * changing the :maximum keyword argument to :count, by analogy
           17 ;;; with CL:REMOVE, CL:SUBSTITUTE, and so on.
           18 ;;;
           19 ;;; * naming the function SPLIT-SEQUENCE rather than PARTITION rather
           20 ;;; than SPLIT.
           21 ;;;
           22 ;;; * adding SPLIT-SEQUENCE-IF and SPLIT-SEQUENCE-IF-NOT.
           23 ;;;
           24 ;;; * The second return value is now an index rather than a copy of a
           25 ;;; portion of the sequence; this index is the `right' one to feed to
           26 ;;; CL:SUBSEQ for continued processing.
           27 
           28 ;;; There's a certain amount of code duplication here, which is kept
           29 ;;; to illustrate the relationship between the SPLIT-SEQUENCE
           30 ;;; functions and the CL:POSITION functions.
           31 
           32 (defpackage :split-sequence
           33   (:use :common-lisp)
           34   (:export #:split-sequence
           35            #:split-sequence-if
           36            #:split-sequence-if-not))
           37 
           38 (in-package :split-sequence)
           39 
           40 (deftype array-index (&optional (length array-dimension-limit))
           41   `(integer 0 (,length)))
           42 
           43 (declaim (ftype (function (&rest t) (values list integer))
           44                 split-sequence split-sequence-if split-sequence-if-not))
           45 
           46 (declaim (ftype (function (function sequence array-index
           47                                     (or null array-index) (or null array-index) boolean)
           48                           (values list integer))
           49                 split-from-start split-from-end))
           50 
           51 (macrolet ((check-bounds (sequence start end)
           52              (let ((length (gensym (string '#:length))))
           53                `(let ((,length (length ,sequence)))
           54                   (check-type ,start unsigned-byte "a non-negative integer")
           55                   (when ,end (check-type ,end unsigned-byte "a non-negative integer or NIL"))
           56                   (unless ,end
           57                     (setf ,end ,length))
           58                   (unless (<= ,start ,end ,length)
           59                     (error "Wrong sequence bounds. start: ~S end: ~S" ,start ,end))))))
           60 
           61   (defun split-sequence (delimiter sequence &key (start 0) (end nil) (from-end nil)
           62                          (count nil) (remove-empty-subseqs nil)
           63                          (test #'eql) (test-not nil) (key #'identity))
           64     "Return a list of subsequences in seq delimited by delimiter.
           65 
           66 If :remove-empty-subseqs is NIL, empty subsequences will be included
           67 in the result; otherwise they will be discarded.  All other keywords
           68 work analogously to those for CL:SUBSTITUTE.  In particular, the
           69 behaviour of :from-end is possibly different from other versions of
           70 this function; :from-end values of NIL and T are equivalent unless
           71 :count is supplied. The second return value is an index suitable as an
           72 argument to CL:SUBSEQ into the sequence indicating where processing
           73 stopped."
           74     (check-bounds sequence start end)
           75     (cond
           76       ((and (not from-end) (null test-not))
           77        (split-from-start (lambda (sequence start)
           78                            (position delimiter sequence :start start :key key :test test))
           79                          sequence start end count remove-empty-subseqs))
           80       ((and (not from-end) test-not)
           81        (split-from-start (lambda (sequence start)
           82                            (position delimiter sequence :start start :key key :test-not test-not))
           83                          sequence start end count remove-empty-subseqs))
           84       ((and from-end (null test-not))
           85        (split-from-end (lambda (sequence end)
           86                          (position delimiter sequence :end end :from-end t :key key :test test))
           87                        sequence start end count remove-empty-subseqs))
           88       (t
           89        (split-from-end (lambda (sequence end)
           90                          (position delimiter sequence :end end :from-end t :key key :test-not test-not))
           91                        sequence start end count remove-empty-subseqs))))
           92 
           93   (defun split-sequence-if (predicate sequence &key (start 0) (end nil) (from-end nil)
           94                             (count nil) (remove-empty-subseqs nil) (key #'identity))
           95     "Return a list of subsequences in seq delimited by items satisfying
           96 predicate.
           97 
           98 If :remove-empty-subseqs is NIL, empty subsequences will be included
           99 in the result; otherwise they will be discarded.  All other keywords
          100 work analogously to those for CL:SUBSTITUTE-IF.  In particular, the
          101 behaviour of :from-end is possibly different from other versions of
          102 this function; :from-end values of NIL and T are equivalent unless
          103 :count is supplied. The second return value is an index suitable as an
          104 argument to CL:SUBSEQ into the sequence indicating where processing
          105 stopped."
          106     (check-bounds sequence start end)
          107     (if from-end
          108         (split-from-end (lambda (sequence end)
          109                           (position-if predicate sequence :end end :from-end t :key key))
          110                         sequence start end count remove-empty-subseqs)
          111         (split-from-start (lambda (sequence start)
          112                             (position-if predicate sequence :start start :key key))
          113                           sequence start end count remove-empty-subseqs)))
          114 
          115   (defun split-sequence-if-not (predicate sequence &key (count nil) (remove-empty-subseqs nil)
          116                                 (from-end nil) (start 0) (end nil) (key #'identity))
          117     "Return a list of subsequences in seq delimited by items satisfying
          118 \(CL:COMPLEMENT predicate).
          119 
          120 If :remove-empty-subseqs is NIL, empty subsequences will be included
          121 in the result; otherwise they will be discarded.  All other keywords
          122 work analogously to those for CL:SUBSTITUTE-IF-NOT.  In particular,
          123 the behaviour of :from-end is possibly different from other versions
          124 of this function; :from-end values of NIL and T are equivalent unless
          125 :count is supplied. The second return value is an index suitable as an
          126 argument to CL:SUBSEQ into the sequence indicating where processing
          127 stopped."
          128     (check-bounds sequence start end)
          129     (if from-end
          130         (split-from-end (lambda (sequence end)
          131                           (position-if-not predicate sequence :end end :from-end t :key key))
          132                         sequence start end count remove-empty-subseqs)
          133         (split-from-start (lambda (sequence start)
          134                             (position-if-not predicate sequence :start start :key key))
          135                           sequence start end count remove-empty-subseqs))))
          136 
          137 (defun split-from-end (position-fn sequence start end count remove-empty-subseqs)
          138   (declare (optimize (speed 3) (debug 0)))
          139   (loop
          140      :for right := end :then left
          141      :for left := (max (or (funcall position-fn sequence right) -1)
          142                        (1- start))
          143      :unless (and (= right (1+ left))
          144                   remove-empty-subseqs) ; empty subseq we don't want
          145      :if (and count (>= nr-elts count))
          146      ;; We can't take any more. Return now.
          147        :return (values (nreverse subseqs) right)
          148      :else
          149        :collect (subseq sequence (1+ left) right) into subseqs
          150        :and :sum 1 :into nr-elts
          151      :until (< left start)
          152    :finally (return (values (nreverse subseqs) (1+ left)))))
          153 
          154 (defun split-from-start (position-fn sequence start end count remove-empty-subseqs)
          155   (declare (optimize (speed 3) (debug 0)))
          156   (let ((length (length sequence)))
          157     (loop
          158        :for left := start :then (+ right 1)
          159        :for right := (min (or (funcall position-fn sequence left) length)
          160                           end)
          161        :unless (and (= right left)
          162                     remove-empty-subseqs) ; empty subseq we don't want
          163        :if (and count (>= nr-elts count))
          164        ;; We can't take any more. Return now.
          165          :return (values subseqs left)
          166        :else
          167          :collect (subseq sequence left right) :into subseqs
          168          :and :sum 1 :into nr-elts
          169        :until (>= right end)
          170      :finally (return (values subseqs right)))))
          171 
          172 (pushnew :split-sequence *features*)