-
Notifications
You must be signed in to change notification settings - Fork 42
/
sequences.lisp
2344 lines (2063 loc) · 85 KB
/
sequences.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(in-package :serapeum)
;;; Some of this API is based on
;;; https://common-lisp.net/project/sequence-iterators/.
(deftype function-name ()
'(and symbol (not (member t nil))))
(deftype key-designator ()
'(or null function-name function))
(deftype test-designator ()
'(or null function-name function))
(deftype signed-array-index ()
"A (possibly negated) array index."
'#.(let ((limit (1- array-dimension-limit)))
`(integer (,(- limit)) (,limit))))
(deftype signed-array-length ()
"A (possibly negated) array length."
'#.(let ((limit (1- array-dimension-limit)))
`(integer ,(- limit) ,limit)))
(defsubst sequencep (x)
"Is X a sequence?"
(typep x 'sequence))
(-> null-if-empty ((or sequence array hash-table))
(values (or sequence array hash-table) boolean &optional))
(defun null-if-empty (xs)
"Return nil if XS is empty, XS otherwise.
If XS was empty the second value is nil; otherwise t.
This function also accepts multidimensional arrays. Arrays are
considered empty if their total size (from `array-total-size`) is
zero.
Hash tables are considered empty if their count is 0."
(etypecase xs
(hash-table
(if (zerop (hash-table-count xs))
(values nil nil)
(values xs t)))
(sequence
(null-if xs 0 :test #'length=))
;; Handle multidimensional arrays.
(array
(if (eql (array-total-size xs) 0)
(values nil nil)
(values xs t)))))
(-> canonicalize-key (key-designator) function)
(defun canonicalize-key (k)
(etypecase-of key-designator k
(null #'identity)
(function-name (fdefinition k))
(function k)))
(-> canonicalize-test (test-designator &optional test-designator)
function)
(defun canonicalize-test (test &optional test-not)
(flet ((canonicalize (test)
(etypecase-of test-designator test
(null #'eql)
(function-name (fdefinition test))
(function test))))
(cond ((and test test-not)
(error "Cannot supply both ~s and ~s to a sequence function"
:test :test-not))
(test (canonicalize test))
(test-not (complement (canonicalize test-not)))
(t #'eql))))
(-> key-test (key-designator test-designator &optional test-designator)
function)
(defun key-test (key test &optional test-not)
"Return a function of two arguments which uses KEY to extract the
part of the arguments to compare, and compares them using TEST."
(declare (optimize (safety 1) (debug 0)))
(let ((key (canonicalize-key key))
(test (canonicalize-test test test-not)))
(if (eql key #'identity) test
(fbind key
(with-two-arg-test (test)
(lambda (x y)
(test (key x) (key y))))))))
(defun make-sequence-like (seq len &rest args &key initial-element
(initial-contents nil ic?))
"Helper function: make a sequence of length LEN having the same type as SEQ."
(seq-dispatch seq
(if ic?
(map 'list #'identity initial-contents)
(make-list len :initial-element initial-element))
(apply #'make-array len :element-type (array-element-type seq) args)
#+(or sbcl abcl) (apply #'sequence:make-sequence-like seq len args)))
(define-do-macro do-vector ((var vec &optional return) &body body)
"Iterate over the items of a vector in a manner similar to `dolist'."
;; TODO See if any other implementations expose something similar.
#+ccl `(ccl:dovector (,var ,vec ,@(unsplice return))
,@body)
#+sbcl `(sb-int:dovector (,var ,vec ,@(unsplice return))
,@body)
#-(or ccl sbcl) `(map nil (lambda (,var) ,@body) ,vec))
(define-do-macro %do-each ((var seq &optional return) &body body)
"Only for Lisps that do not support extensible sequences."
(once-only (seq)
`(seq-dispatch ,seq
(dolist (,var ,seq)
,@body)
(do-vector (,var ,seq)
,@body))))
(define-do-macro do-each/map ((var seq &optional return) &body body)
"The simple, out-of-line version."
(with-thunk (body var)
`(map nil ,body ,seq)))
(defmacro do-each ((var seq &optional return) &body body &environment env)
"Iterate over the elements of SEQ, a sequence.
If SEQ is a list, this is equivalent to `dolist'."
(unless (speed-matters? env)
(return-from do-each
`(do-each/map (,var ,seq ,@(unsplice return))
,@body)))
;; We hoist the body and use sb-ext:muffle-conditions to prevent
;; SBCL from spamming us with code deletion notes. (It may also be
;; desirable in itself to avoid needless code duplication in Lisps
;; without type inference.)
(with-thunk (body var)
(let ((iter-spec `(,var ,seq ,@(unsplice return))))
`(locally (declare #+sbcl (sb-ext:muffle-conditions sb-ext:code-deletion-note))
#+(or sbcl abcl)
(sequence:dosequence ,iter-spec
(,body ,var))
#-(or sbcl abcl)
(%do-each ,iter-spec
(,body ,var))))))
(declaim (inline map-subseq))
(defun map-subseq (fn seq &optional start end from-end)
"Helper function to map SEQ between START and END."
(declare (type (or null array-index) start end)
(optimize (debug 0) (safety 1)
(compilation-speed 0)))
;; (when (and start end)
;; (assert (<= start end)))
(let ((start (or start 0))
(fn (ensure-function fn)))
(fbind (fn)
(seq-dispatch seq
(if (null end)
(if from-end
(list-map-from-end/bordeaux fn seq :start start)
(dolist (item (nthcdr start seq))
(fn item)))
(if from-end
(list-map-from-end/bordeaux fn seq :start start :end end)
(loop for item in (nthcdr start seq)
for i below (- end start)
do (fn item))))
(with-subtype-dispatch vector
(simple-bit-vector
bit-vector
(simple-array character (*))
simple-base-string)
seq
(let ((end (or end (length seq))))
(if from-end
(loop for i downfrom (1- end) to start
do (fn (vref seq i)))
(loop for i from start below end
do (fn (vref seq i))))))
(let ((end (or end (length seq))))
(if from-end
(loop for i downfrom (1- end) to start
do (fn (elt seq i)))
(loop for i from start below end
do (fn (elt seq i)))))))))
(declaim (notinline map-subseq))
(define-do-macro do-subseq ((var seq &optional return
&key start end from-end)
&body body)
(declare #+sbcl (sb-ext:muffle-conditions style-warning))
`(locally (declare (inline map-subseq))
(map-subseq
(lambda (,var)
,@body)
,seq
,start ,end
,from-end)))
;;; Define a protocol for accumulators so we can write functions like
;;; `assort', `partition', &c. generically.
(defmacro with-list-bucket ((seq) &body body)
"Wrap BODY with inlined bucket accessors for lists."
(declare (ignore seq))
`(flet ((make-bucket (seq &optional (init nil initp))
(declare (ignore seq))
(if initp
(queue init)
(queue)))
(bucket-push (seq item bucket)
(declare (ignore seq)
(queue bucket))
(enq item bucket))
(bucket-seq (seq bucket)
(declare (ignore seq)
(queue bucket))
(qlist bucket)))
(declare (ignorable #'make-bucket #'bucket-push #'bucket-seq))
(declare (inline make-bucket bucket-push bucket-seq))
,@body))
(defmacro with-string-bucket ((seq) &body body)
"Wrap BODY with inlined bucket accessors for strings."
(declare (ignore seq))
`(flet ((make-bucket (seq &optional (init nil initp))
(let ((stream
(make-string-output-stream
:element-type (array-element-type seq))))
(and initp (write-char init stream))
stream))
(bucket-push (seq item bucket)
(declare (ignore seq))
(write-char item bucket))
(bucket-seq (seq bucket)
(declare (ignore seq))
(get-output-stream-string bucket)))
(declare (ignorable #'make-bucket #'bucket-push #'bucket-seq))
(declare (inline make-bucket bucket-push bucket-seq))
,@body))
(defmacro with-vector-bucket ((seq) &body body)
"Wrap BODY with inlined bucket accessors for vectors (including strings)."
`(if (stringp ,seq)
(with-string-bucket (,seq)
,@body)
(flet ((make-bucket (seq &optional (init nil initp))
(with-boolean (initp)
(make-array (boolean-if initp 1 0)
:element-type (array-element-type seq)
:adjustable t
:fill-pointer (boolean-if initp 1 0)
:initial-contents (and initp (list init)))))
(bucket-push (seq item bucket)
(declare (ignore seq))
(vector-push-extend item bucket))
(bucket-seq (seq bucket)
(declare (ignore seq))
bucket))
(declare (ignorable #'make-bucket #'bucket-push #'bucket-seq))
(declare (inline make-bucket bucket-push bucket-seq))
,@body)))
(defmacro with-sequence-bucket ((seq) &body body)
"Wrap BODY with inlined bucket accessors for generic sequences.
This might not seem worthwhile, and it's not for `bucket-seq', but for
`bucket-push' (and even `make-bucket') it is, since accumulating for
generic sequences just uses queues."
(declare (ignore seq))
`(flet ((make-bucket (seq &optional (init nil initp))
(declare (ignore seq))
(if initp
(queue init)
(queue)))
(bucket-push (seq item bucket)
(declare (ignore seq)
(queue bucket))
(enq item bucket))
(bucket-seq (seq bucket)
(declare (queue bucket))
(let ((len (qlen bucket)))
(make-sequence-like seq len :initial-contents (qlist bucket)))))
(declare (ignorable #'make-bucket #'bucket-push #'bucket-seq))
(declare (inline make-bucket bucket-push bucket-seq))
,@body))
(defmacro with-specialized-buckets ((seq) &body body)
"Ensure BODY is run with the appropriate specialized, inlined
versions of the bucket accessors.
This is only likely to be worthwhile around a loop; if you're calling
a bucket accessor once or twice the code bloat isn't worth it."
(multiple-value-bind (body decls) (parse-body body)
`(locally
(declare #+sbcl (sb-ext:muffle-conditions sb-ext:code-deletion-note))
,@decls
(with-type-declarations-trusted ()
(seq-dispatch ,seq
(with-list-bucket (,seq)
,@body)
(with-vector-bucket (,seq)
,@body)
(with-sequence-bucket (,seq)
,@body))))))
;;; Fallback versions for non-specialized code.
(defun make-bucket (seq &optional (init nil initp))
"Return a \"bucket\" suitable for collecting elements from SEQ.
If SEQ is restricted as to the type of elements it can hold (for
example, if SEQ is an array with an element type) the same restriction
will apply to the bucket."
(with-specialized-buckets (seq)
(if initp
(make-bucket seq init)
(make-bucket seq))))
(defun bucket-push (seq item bucket)
"Insert ITEM at the end of BUCKET according to SEQ."
(with-specialized-buckets (seq)
(bucket-push seq item bucket)))
(defun bucket-seq (seq bucket)
"Return a sequence \"like\" SEQ using the elements of BUCKET.
Note that it is not safe to call the function more than once on the
same bucket."
(with-specialized-buckets (seq)
(bucket-seq seq bucket)))
;;; Not currently used, but probably should be.
(defun bucket-append (seq items bucket)
"Append ITEMS to the end of BUCKET according to SEQ.
The items will appear, together and in the same order, in the
sequence taken from the bucket by BUCKET-SEQ."
(cond ((and (listp seq)
(listp items))
(qappend bucket items))
((stringp seq)
(write-string items bucket))
(t
(map nil (lambda (item)
(bucket-push seq item bucket))
items))))
(-> nsubseq
(sequence array-index &optional (or null array-length))
sequence)
(defun nsubseq (seq start &optional end)
"Return a subsequence that may share structure with SEQ.
Note that `nsubseq' gets its aposematic leading `n' not because it is
itself destructive, but because, unlike `subseq', destructive
operations on the subsequence returned may mutate the original.
`nsubseq' also works with `setf', with the same behavior as
`replace'."
(seq-dispatch seq
(cond (end (subseq seq start end))
((= start 0) seq)
(t (nthcdr start seq)))
(let* ((len (length seq))
(end (or end len)))
(if (and (= start 0) (= end len))
seq
;; TODO Would it be better to undisplace the vector first?
(make-array (- end start)
:element-type (array-element-type seq)
:displaced-to seq
:displaced-index-offset start)))
(let ((end (length seq)))
(if (and (= start 0)
(or (no end)
(= end (length seq))))
seq
(subseq seq start end)))))
(defun (setf nsubseq) (value seq start &optional end)
"Destructively set SEQ between START and END to VALUE.
Uses `replace' internally."
(replace seq value :start1 start :end1 end)
value)
(defun filter/counted (pred seq &rest args
&key count from-end (start 0) end
(key #'identity))
"Helper for FILTER."
(cond
;; Simple cases.
((= count 0) (make-sequence-like seq 0))
((> count (length seq)) (apply #'filter pred seq :count nil args))
(t (fbind (pred)
(let ((ret (make-bucket seq)))
(with-item-key-function (key)
(with-specialized-buckets (seq)
(do-subseq (item seq nil :start start :end end :from-end from-end)
(when (pred (key item))
(bucket-push seq item ret)
(when (zerop (decf count))
(return))))))
(let ((seq2 (bucket-seq seq ret)))
(if from-end (nreverse seq2) seq2)))))))
(defun filter (pred seq &rest args &key count &allow-other-keys)
"Almost, but not quite, an alias for `remove-if-not'.
The difference is the handling of COUNT: for `filter', COUNT is the
number of items to *keep*, not remove.
(remove-if-not #'oddp '(1 2 3 4 5) :count 2)
=> '(1 3 5)
(filter #'oddp '(1 2 3 4 5) :count 2)
=> '(1 3)"
(declare (dynamic-extent pred))
(if count
(apply #'filter/counted pred seq args)
(apply #'remove-if-not pred seq args)))
(define-compiler-macro filter (&whole decline
pred seq
&rest args
&key count
&allow-other-keys)
"In the absence of COUNT, expand directly to `remove-if-not'."
(if (null count)
`(remove-if-not ,pred ,seq ,@args)
decline))
(declaim (inline filter/swapped-arguments))
(defun filter/swapped-arguments (seq pred &rest args &key &allow-other-keys)
(apply #'filter pred seq args))
(define-modify-macro filterf (pred &rest args)
filter/swapped-arguments
"Modify-macro for FILTER.
The place designed by the first argument is set to the result of
calling FILTER with PRED, the place, and ARGS.")
(defun keep (item seq &rest args &key (test #'eql) from-end key count
&allow-other-keys)
"Almost, but not quite, an alias for `remove` with `:test-not` instead of `:test`.
The difference is the handling of COUNT. For keep, COUNT is the number of items to keep, not remove.
(remove 'x '(x y x y x y) :count 2)
=> '(y y x y)
(keep 'x '(x y x y x y) :count 2)
=> '(x x)
`keep' becomes useful with the KEY argument:
(keep 'x ((x 1) (y 2) (x 3)) :key #'car)
=> '((x 1) (x 3))"
(declare (ignore from-end key))
(declare (dynamic-extent key test))
(let ((args (remove-from-plist args :test)))
(if (null count)
(apply #'remove item seq :test-not test args)
(fbind ((test (partial test item)))
(declare (dynamic-extent #'test))
(apply #'filter #'test seq :count count args)))))
(define-compiler-macro keep (&whole decline
item seq
&rest args
&key (test '#'eql) count
&allow-other-keys)
"In the absence of COUNT, expand directly to `remove'."
(if (null count)
`(remove ,item ,seq :test-not ,test ,@(remove-from-plist args :test))
decline))
(-> single (sequence) boolean)
(defsubst single (seq)
"Is SEQ a sequence of one element?"
(seq-dispatch seq
(and seq (endp (cdr seq)))
(= (length seq) 1)))
(deftype single ()
'(and sequence (satisfies single)))
(defun only-elt (seq)
"Return the only element of SEQ.
If SEQ is empty, or contains more than one element, signal an error."
(flet ((fail ()
(error 'type-error
:expected-type 'single
:datum seq)))
(declare (dynamic-extent #'fail))
(seq-dispatch seq
(if (and seq
(endp (rest seq)))
(first seq)
(fail))
(if (= (length seq) 1)
(elt seq 0)
(fail)))))
;;; TODO Export once you're sure of the name.
(defun rotation (seq n)
"Like `rotate', but non-destructive."
(rotate (copy-seq seq) n))
(defun partition (pred seq &key (start 0) end (key #'identity))
"Partition elements of SEQ into those for which PRED returns true
and false.
Return two values, one with each sequence.
Exactly equivalent to:
(values (remove-if-not predicate seq) (remove-if predicate seq))
except it visits each element only once.
Note that `partition` is not just `assort` with an up-or-down
predicate. `assort` returns its groupings in the order they occur in
the sequence; `partition` always returns the “true” elements first.
(assort '(1 2 3) :key #'evenp) => ((1 3) (2))
(partition #'evenp '(1 2 3)) => (2), (1 3)"
(declare (dynamic-extent pred key))
(fbind ((test (compose pred (canonicalize-key key))))
(let ((pass (make-bucket seq))
(fail (make-bucket seq)))
(with-specialized-buckets (seq)
(do-subseq (item seq nil :start start :end end)
(if (test item)
(bucket-push seq item pass)
(bucket-push seq item fail))))
(values (bucket-seq seq pass) (bucket-seq seq fail)))))
(defun partitions (preds seq &key (start 0) end (key #'identity))
"Generalized version of PARTITION.
PREDS is a list of predicates. For each predicate, `partitions'
returns a filtered copy of SEQ. As a second value, it returns an extra
sequence of the items that do not match any predicate.
Items are assigned to the first predicate they match."
(declare (dynamic-extent preds key))
(with-item-key-function (key)
(with-specialized-buckets (seq)
(let ((buckets (loop for nil in preds collect (make-bucket seq)))
(extra (make-bucket seq)))
(do-subseq (item seq nil :start start :end end)
(loop for pred in preds
for bucket in buckets
for fn = (ensure-function pred)
if (funcall fn (key item))
return (bucket-push seq item bucket)
finally (bucket-push seq item extra)))
(values (mapcar-into (lambda (bucket)
(bucket-seq seq bucket))
buckets)
(bucket-seq seq extra))))))
(defconstructor agroup
"Auxiliary data structure for `assort'. A pair of an exemplar (to
compare against) and a bucket of matching items. Note that while the
agroup is immutable, the bucket itself is mutable."
(exemplar t)
(bucket t))
(defun assort (seq &key (key #'identity) (test #'eql) (start 0) end hash
&aux (orig-test test))
"Return SEQ assorted by KEY.
(assort (iota 10)
:key (lambda (n) (mod n 3)))
=> '((0 3 6 9) (1 4 7) (2 5 8))
Groups are ordered as encountered. This property means you could, in
principle, use `assort' to implement `remove-duplicates' by taking the
first element of each group:
(mapcar #'first (assort list))
≡ (remove-duplicates list :from-end t)
However, if TEST is ambiguous (a partial order), and an element could
qualify as a member of more than one group, then it is not guaranteed
that it will end up in the leftmost group that it could be a member
of.
(assort '(1 2 1 2 1 2) :test #'<=)
=> '((1 1) (2 2 1 2))
The default algorithm used by `assort' is, in the worst case, O(n) in
the number of groups. If HASH is specified, then a hash table is used
instead. However TEST must be acceptable as the `:test' argument to
`make-hash-table'."
(declare (dynamic-extent key test))
(fbind (test)
(with-item-key-function (key)
(with-boolean (hash)
(let ((groups (queue))
(table (boolean-if hash (make-hash-table :test orig-test) nil))
last-group)
(declare (ignorable table))
(with-specialized-buckets (seq)
(do-subseq (item seq nil :start start :end end)
(let ((kitem (key item)))
(if-let ((group
(if (match last-group
((agroup exemplar _)
(test kitem exemplar)))
last-group
(boolean-if
hash
(gethash kitem table)
(find-if
(lambda (exemplar)
(test kitem exemplar))
(qlist groups)
:key #'agroup-exemplar)))))
(progn
(setf last-group group)
(bucket-push seq item (agroup-bucket group)))
(let ((new-group (agroup kitem (make-bucket seq item))))
(boolean-when hash
(setf (gethash kitem table) new-group))
(enq new-group groups)))))
(mapcar-into (lambda (group)
(bucket-seq seq (agroup-bucket group)))
(qlist groups))))))))
(defun list-runs (list start end key test count compare-last)
(declare ((and fixnum unsigned-byte) count))
(when (zerop count)
(return-from list-runs nil))
(fbind ((test (key-test key test)))
(declare (dynamic-extent #'test))
(with-boolean (compare-last)
;; This is a little more complicated than you might expect,
;; because we need to keep hold of the first element of each list.
(let ((runs
(nlet rec ((runs nil)
(count count)
(list
(nthcdr start
(if end
(ldiff list (nthcdr (- end start) list))
list))))
(if (endp list) runs
(let ((y (car list)))
(if (null runs)
(rec (list (list y))
count
(cdr list))
(let ((x (caar runs)))
(if (test x y)
(rec (cons
(boolean-if compare-last
(cons y (car runs))
(list* x y (cdar runs)))
(cdr runs))
count
(rest list))
(if (zerop (1- count))
runs
(rec (list*
(list y)
(boolean-if compare-last
(nreverse (car runs))
(cons (caar runs)
(nreverse (cdar runs))))
(cdr runs))
(1- count)
(rest list)))))))))))
(nreverse
(boolean-if compare-last
(cons (nreverse (car runs)) (cdr runs))
(cons (cons (caar runs)
(nreverse (cdar runs)))
(cdr runs))))))))
(defun runs (seq &key (start 0) end (key #'identity) (test #'eql) compare-last
(count most-positive-fixnum))
"Return a list of runs of similar elements in SEQ.
The arguments START, END, and KEY are as for `reduce'.
(runs '(head tail head head tail))
=> '((head) (tail) (head head) (tail))
By defualt, the function TEST is called with the first element of the
run as its first argument.
(runs #(10 1 5 10 1) :test #'>)
=> (#(10 1 5) #(10))
COMPARE-LAST changes this behavior to test against the previous
element of the run:
(runs #(10 1 5 10 1) :test #'> :compare-last t)
(#(10 1) #(5) #(10))
The COUNT argument limits how many runs are returned.
(runs '(head tail tail head head tail) :count 2)
=> '((head) (tail tail))"
(declare ((and fixnum unsigned-byte) count))
(declare (dynamic-extent key test))
(cond ((zerop count) (list))
((emptyp seq) (list seq))
(t (seq-dispatch seq
(list-runs seq start end key test count compare-last)
(fbind ((test (key-test key test)))
(declare (dynamic-extent #'test))
(collecting*
(nlet runs ((start start)
(count count))
(when (plusp count)
(let* ((elt (elt seq start))
(run-end-pos
(position-if-not
(if compare-last
(lambda (x)
(when (test elt x)
(setf elt x)
t))
(partial #'test elt))
seq
:start (1+ start)
:end end)))
(if (null run-end-pos)
(collect (subseq seq start end))
(progn
(collect (subseq seq start run-end-pos))
(runs run-end-pos (1- count)))))))))))))
(defun batches (seq n &key (start 0) end even)
"Return SEQ in batches of N elements.
(batches (iota 11) 2)
=> ((0 1) (2 3) (4 5) (6 7) (8 9) (10))
If EVEN is non-nil, then SEQ must be evenly divisible into batches of
size N, with no leftovers."
#+sbcl (declare (sb-ext:muffle-conditions sb-ext:code-deletion-note))
(check-type n (integer 0 *))
(check-type start (integer 0 *))
(flet ((uneven ()
(error "A ~@[sub~*~]sequence of length ~a cannot be evenly divided into batches of size ~a."
end
(- (or end (length seq)) start)
n)))
(declare (dynamic-extent #'uneven))
(flet ((check-bounds-even (start end)
(when even
(unless (zerop (rem (- end start) n))
(uneven)))))
(declare (inline check-bounds-even))
(seq-dispatch seq
(let ((seq (nthcdr start seq)))
(if (null end)
(with-boolean (even)
(loop while seq
collect (loop for i below n
for (elt . rest) on seq
collect elt
finally (setf seq rest)
(boolean-when even
(unless (= i n)
(uneven))))))
(progn
(check-bounds-even start end)
(with-boolean (even)
(loop while seq
for i from start below end by n
collect
(loop with m = (min n (- end i))
for i below m
for (elt . rest) on seq
collect elt
finally (setf seq rest)
(boolean-when even
(unless (= i m)
(uneven)))))))))
(let ((end (or end (length seq))))
(check-bounds-even start end)
(nlet batches ((i start)
(acc '()))
(if (>= i end)
(nreverse acc)
(batches (+ i n)
(cons (subseq seq i (min (+ i n) end))
acc)))))))))
(-> frequencies (sequence &key (:key function) &allow-other-keys)
(values hash-table array-length))
(defun frequencies (seq &rest hash-table-args &key (key #'identity)
&allow-other-keys)
"Return a hash table with the count of each unique item in SEQ.
As a second value, return the length of SEQ.
From Clojure."
(let ((total 0)
;; Using multiple-value-call lets us specify defaults while
;; still ensuring the caller can override them.
(table (multiple-value-call #'make-hash-table
(values-list (remove-from-plist hash-table-args :key))
:size (values (floor (length seq) 2))
:test 'equal)))
(declare (fixnum total))
(with-item-key-function (key)
(if (typep seq 'bit-vector)
(with-subtype-dispatch bit-vector (simple-bit-vector) seq
(setf (gethash (key 0) table) (count 0 seq)
(gethash (key 1) table) (count 1 seq)
total (length seq)))
(do-each (elt seq)
(incf total)
(incf (gethash (key elt) table 0)))))
(values table total)))
(defun scan (fn seq
&rest args
&key from-end
(start 0)
(end (length seq))
(initial-value nil initial-value-supplied?)
&allow-other-keys)
"Return the partial reductions of SEQ.
Each element of the result sequence is the result of calling `reduce'
on the elements of the sequence up to that point (inclusively).
(reduce #'+ '(1)) => 1
(reduce #'+ '(1 2)) => 3
(reduce #'+ '(1 2 3)) => 6
(reduce #'+ '(1 2 3 4)) => 10
(scan #'+ '(1 2 3 4)) => '(1 3 6 10)
The result of calling `scan` on an empty sequence is always an empty
sequence, however.
(reduce #'+ '()) => 0
(scan #'+ '()) => '()
This is sometimes called a \"prefix sum\", \"cumulative sum\", or
\"inclusive scan\".
From APL."
(declare (dynamic-extent fn))
(fbind (fn)
(if (= start end)
(if initial-value-supplied?
;; NB reduce does not apply the key to the initial value
;; if the sequence is empty.
(list initial-value)
(list))
(collecting
(collect
(apply #'reduce
(if from-end
(lambda (x y)
(collect y)
(fn x y))
(lambda (x y)
(collect x)
(fn x y)))
seq
args))))))
(defsubst nub (seq &rest args &key start end key (test #'equal))
"Remove duplicates from SEQ, starting from the end.
That means, for each duplicate, the first occurrence will be the kept, and subsequent occurrences will be discarded.
TEST defaults to `equal'.
From Haskell."
(declare (ignore start end key))
(apply #'remove-duplicates seq :from-end t :test test args))
(define-compiler-macro nub (seq &rest args &key (test '#'equal) &allow-other-keys)
`(remove-duplicates ,seq :from-end t :test ,test ,@args))
(defun gcp (seqs &key (test #'eql))
"The greatest common prefix of SEQS.
If there is no common prefix, return NIL."
(if (emptyp seqs) nil
(let ((test (ensure-function test)))
(labels ((gcp (x y)
(let ((miss (mismatch x y :test test)))
(cond ((not miss) x)
((> miss 0) (subseq x 0 miss))
(t nil)))))
(block nil
(reduce
(lambda (x y)
(or (gcp x y)
(return)))
seqs))))))
(defun gcs (seqs &key (test #'eql))
"The greatest common suffix of SEQS.
If there is no common suffix, return NIL."
(if (emptyp seqs) nil
(let ((test (ensure-function test)))
(labels ((gcs (x y)
(let ((miss (mismatch x y :from-end t :test test)))
(cond ((not miss) x)
((< miss (length x))
(subseq x miss))
(t nil)))))
(block nil
(reduce
(lambda (x y)
(or (gcs x y)
(return)))
seqs))))))
(-> of-length (array-length) function)
(defun of-length (length)
"Return a predicate that returns T when called on a sequence of
length LENGTH.
(funcall (of-length 3) '(1 2 3)) => t
(funcall (of-length 1) '(1 2 3)) => nil"
(lambda (seq)
(sequence-of-length-p seq length)))
(define-compiler-macro of-length (&whole call length &environment env)
(if (constantp length env)
`(lambda (seq)
(sequence-of-length-p seq ,length))
call))
(defmacro length-gt (cmp offset-fn seqs)
`(nlet rec ((prev most-positive-fixnum)
(seqs ,seqs))
(if (endp seqs) t
(destructuring-bind (seq . seqs) seqs
(etypecase seq
(array-length
(and (,cmp prev seq)
(rec seq seqs)))
(list
(let ((len
;; Get the length of SEQ, but only up to LAST.
(loop with len = 0
repeat (,offset-fn prev)
until (endp seq) do
(incf len)
(pop seq)
finally (return len))))
(and (,cmp prev len)
(rec len seqs))))
(sequence
(let ((len (length seq)))
(and (,cmp prev len)
(rec len seqs)))))))))
(defun length> (&rest seqs)
"Is each length-designator in SEQS longer than the next?
A length designator may be a sequence or an integer."
(length-gt > identity seqs))
(defun length>= (&rest seqs)
"Is each length-designator in SEQS longer or as long as the next?
A length designator may be a sequence or an integer."
(length-gt >= 1+ seqs))
(defun length< (&rest seqs)
"Is each length-designator in SEQS shorter than the next?
A length designator may be a sequence or an integer."
(declare (dynamic-extent seqs))
(apply #'length> (reverse seqs)))
(defun length<= (&rest seqs)
"Is each length-designator in SEQS as long or shorter than the next?
A length designator may be a sequence or an integer."
(declare (dynamic-extent seqs))
(apply #'length>= (reverse seqs)))
(defun longer (x y)
"Return the longer of X and Y.
If X and Y are of equal length, return X.
If X and Y are lists, this will only traverse the shorter of X and Y."
(check-type x sequence)
(check-type y sequence)
(cond ((and (listp x) (listp y))
(nlet longer ((xs x)
(ys y))
(cond ((and (endp xs) (endp ys)) x)
((endp ys) x)
((endp xs) y)
(t (longer (rest xs) (rest ys))))))
(t (if (length>= x y) x y))))
(defun shorter (x y)
"Return the shorter of X and Y."
(cond ((and (listp x) (listp y))
(nlet shorter ((xs x)
(ys y))