picl
2024-10-12
Python Itertools in Common Lisp
picl
Anish Moorthy [email protected]
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 cl-cont
.
cl-itertools
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
Installation
PICL is in Quicklisp, and can be installed as follows
(ql:quickload :picl)
Do not :use
this package: it might export new symbols in the future. You have
been forewarned.
Documentation
Thanks to Staple you can read the documentation online or build it yourself like so
(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
Testing
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.
By example
(let ((it (make-iterator '(1 2)))) (next it) ;; (values 1 t) (next it) ;; (values 2 t) (next it)) ;; (values nil nil)
After returning nil
, all further next
calls should also produce nil
as
quickly as possible. Furthermore when the alive indicator is nil
, the payload
should be ignored.
To create iterators over your own objects, specialize the make-iterator
generic function appropriately. For instance, the make-iterator
definition for
lists is
(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 in-it
driver is also provided for Iterate
through the picl/iterate
system.
(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 in-it
clause to perform conversions like this when specified
Future Work
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?
License
This project is provided under the MIT License (see LICENSE.md)