diff --git a/README.md b/README.md index f52d46d..d57cd9d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,14 @@ Include this in your emacs settings to get syntax highlighting: * [-max](#-max-list) `(list)` * [-max-by](#-max-by-comparator-list) `(comparator list)` +### Unfolding + + +Operations dual to reductions, building lists from seed value rather than consuming a list to produce a single value. + +* [-iterate](#-iterate-fun-init-n) `(fun init n)` +* [-unfold](#-unfold-fun-seed) `(fun seed)` + ### Predicates * [-any?](#-any-pred-list) `(pred list)` @@ -564,6 +572,45 @@ comparing them. ``` +## Unfolding + + +Operations dual to reductions, building lists from seed value rather than consuming a list to produce a single value. + +#### -iterate `(fun init n)` + +Return a list of iterated applications of `fun` to `init`. + +This means a list of form: + '(init (fun init) (fun (fun init)) ...) + +`n` is the length of the returned list. + +```cl +(-iterate '1+ 1 10) ;; => '(1 2 3 4 5 6 7 8 9 10) +(-iterate (lambda (x) (+ x x)) 2 5) ;; => '(2 4 8 16 32) +(--iterate (* it it) 2 5) ;; => '(2 4 16 256 65536) +``` + +#### -unfold `(fun seed)` + +Build a list from `seed` using `fun`. + +This is "dual" operation to `-reduce-r`: while -reduce-r +consumes a list to produce a single value, `-unfold` takes a +seed value and builds a (potentially infinite!) list. + +`fun` should return `nil` to stop the generating process, or a +cons (`a` . `b`), where `a` will be prepended to the result and `b` is +the new seed. + +```cl +(-unfold (lambda (x) (unless (= x 0) (cons x (1- x)))) 10) ;; => '(10 9 8 7 6 5 4 3 2 1) +(--unfold (when it (cons it (cdr it))) '(1 2 3 4)) ;; => '((1 2 3 4) (2 3 4) (3 4) (4)) +(--unfold (when it (cons it (butlast it))) '(1 2 3 4)) ;; => '((1 2 3 4) (1 2 3) (1 2) (1)) +``` + + ## Predicates #### -any? `(pred list)` diff --git a/dash.el b/dash.el index 4d4ef4e..34c797c 100644 --- a/dash.el +++ b/dash.el @@ -1089,6 +1089,45 @@ The items for the comparator form are exposed as \"it\" and \"other\"." (declare (debug (sexp form))) `(-min-by (lambda (it other) ,form) ,list)) +(defun -iterate (fun init n) + "Return a list of iterated applications of FUN to INIT. + +This means a list of form: + '(init (fun init) (fun (fun init)) ...) + +N is the length of the returned list." + (if (= n 0) nil + (let ((r (list init))) + (--dotimes (1- n) + (push (funcall fun (car r)) r)) + (nreverse r)))) + +(defmacro --iterate (form init n) + "Anaphoric version of `-iterate'." + (declare (debug (sexp form form))) + `(-iterate (lambda (it) ,form) ,init ,n)) + +(defun -unfold (fun seed) + "Build a list from SEED using FUN. + +This is \"dual\" operation to `-reduce-r': while -reduce-r +consumes a list to produce a single value, `-unfold' takes a +seed value and builds a (potentially infinite!) list. + +FUN should return `nil' to stop the generating process, or a +cons (A . B), where A will be prepended to the result and B is +the new seed." + (let ((last (funcall fun seed)) r) + (while last + (push (car last) r) + (setq last (funcall fun (cdr last)))) + (nreverse r))) + +(defmacro --unfold (form seed) + "Anaphoric version of `-unfold'." + (declare (debug (sexp form))) + `(-unfold (lambda (it) ,form) ,seed)) + (defun -cons-pair? (con) "Return non-nil if CON is true cons pair. That is (A . B) where B is not a list." @@ -1367,6 +1406,10 @@ structure such as plist or alist." "--max-by" "-min-by" "--min-by" + "-iterate" + "--iterate" + "-unfold" + "--unfold" "-cons-pair?" "-cons-to-list" "-value-to-list" diff --git a/dev/examples.el b/dev/examples.el index c0ae5bf..3a932a0 100644 --- a/dev/examples.el +++ b/dev/examples.el @@ -199,6 +199,19 @@ (--max-by (> (car it) (car other)) '((1 2 3) (2) (3 2))) => '(3 2) (--max-by (> (length it) (length other)) '((1 2 3) (2) (3 2))) => '(1 2 3))) +(def-example-group "Unfolding" + "Operations dual to reductions, building lists from seed value rather than consuming a list to produce a single value." + + (defexamples -iterate + (-iterate '1+ 1 10) => '(1 2 3 4 5 6 7 8 9 10) + (-iterate (lambda (x) (+ x x)) 2 5) => '(2 4 8 16 32) + (--iterate (* it it) 2 5) => '(2 4 16 256 65536)) + + (defexamples -unfold + (-unfold (lambda (x) (unless (= x 0) (cons x (1- x)))) 10) => '(10 9 8 7 6 5 4 3 2 1) + (--unfold (when it (cons it (cdr it))) '(1 2 3 4)) => '((1 2 3 4) (2 3 4) (3 4) (4)) + (--unfold (when it (cons it (butlast it))) '(1 2 3 4)) => '((1 2 3 4) (1 2 3) (1 2) (1)))) + (def-example-group "Predicates" nil (defexamples -any? (-any? 'even? '(1 2 3)) => t