Python Itertools in Common Lisp (v1.0.0). Pronounced like "pickle"
A (very nearly) complete port of Python's itertools package, complete with laziness where applicable.
This project currently lives on Github. Pull requests welcome!
Objectives and Rationale
PICL aims to provide a complete port of itertools, complete with laziness,
without any reliance on
and snakes, provide similar functionality.
Unfortunately both libraries rely on
cl-cont, meaning they wont always play
nice with the condition system, and
cl-itertools remains very incomplete on
top of that
PICL is in Quicklisp, and can be installed as follows
:use this package: it might export new symbols in the future. You have
(staple:generate :picl :if-exists :supersede)
If you don't have PICL's dependencies loaded into your image yet, you'll get some harmless warnings about invalid definitions
A fairly comprehensive test suite written with FiveAM is provided. You can run it yourself either manually or through asdf
;; The easy way (asdf:test-system :picl) ;; The slightly less easy way (ql:quickload :picl/tests) (fiveam:run! 'picl/tests:suite)
Concepts and How-To
An "iterator" in PICL is simply a thunk producing two values: the payload and the alive-indicator. The alive-indicator should be truthy until after the iterator is consumed.
(let ((it (make-iterator '(1 2)))) (next it) ;; (values 1 t) (next it) ;; (values 2 t) (next it)) ;; (values nil nil)
nil, all further
next calls should also produce
quickly as possible. Furthermore when the alive indicator is
nil, the payload
should be ignored.
To create iterators over your own objects, specialize the
generic function appropriately. For instance, the
make-iterator definition for
(defmethod make-iterator ((obj list)) (lambda () (if obj (values (prog1 (car obj) (setf obj (cdr obj))) t) (values nil nil))))
Specializations for lists and vectors are predefined. A universal
driver is also provided for Iterate
(ql:quickload '(:picl :picl/iterate)) ;; The "iterate" package has been :use'd here (iterate (for i in-it (picl:permutations '(1 2 3))) (collect i)) ;; (#(1 2 3) #(1 3 2) #(2 1 3) #(2 3 1) #(3 1 2) #(3 2 1))
Note: All of the combinatoric iterators produce vectors, which can be
annoying because those are second-class citizens in CL (you can't destructure
them, for instance). To get around this, you can wrap the iterator in
(picl:map #'iter-to-list <>)
(picl:iter-to-list (picl:map #'picl:iter-to-list (picl:permutations '(1 2 3)))) ;; ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
It's a bit clunky for sure, so in the future I might extend the
clause to perform conversions like this when specified
Functions still missing from Python's itertools (due to laziness: if you need these drop an issue/PR and I'll get around to implementing them)
Extensions to library
- Port the more-itertools recipes found at bottom of the Python itertools package
- Port the more-iterools package (seems like a big job)
- Some sort of integration with fset's sequence type?
This project is provided under the MIT License (see LICENSE.md)