From: mark.hoemmen on
Greetings!

I'd like an iterator over all the combinations of the elements of a set
(stored as, say, a list). I know the recursive definition of the set
of all combinations of a set, but I'd like to avoid the combinatorial
space explosion of actually _constructing_ the set of all combinations.


Is there an easy way of defining such an iterator using, say,
continuations?

Can this idea be extended, say via a macro, so that I can automatically
transform any process that creates successive data from one or more
data into an iterator?

mfh

From: Pascal Bourguignon on
"mark.hoemmen(a)gmail.com" <mark.hoemmen(a)gmail.com> writes:
> Greetings!
>
> I'd like an iterator over all the combinations of the elements of a set
> (stored as, say, a list). I know the recursive definition of the set
> of all combinations of a set, but I'd like to avoid the combinatorial
> space explosion of actually _constructing_ the set of all combinations.
>
>
> Is there an easy way of defining such an iterator using, say,
> continuations?
>
> Can this idea be extended, say via a macro, so that I can automatically
> transform any process that creates successive data from one or more
> data into an iterator?

http://darcs.informatimago.com/darcs/public/lisp/common-lisp/combination.lisp


--
__Pascal Bourguignon__ http://www.informatimago.com/

"What is this talk of "release"? Klingons do not make software
"releases". Our software "escapes" leaving a bloody trail of
designers and quality assurance people in its wake."
From: mark.hoemmen on
On Jan 1, 2:47 pm, Pascal Bourguignon <p...(a)informatimago.com> wrote:
> http://darcs.informatimago.com/darcs/public/lisp/common-lisp/combinat...

what i was thinking of for the second question (generalizing the idea
of iterator over a process) was something like this. If you want a
list of all the combinations of the elements of a given list, you do
something like this:

(defun combinations (list)
(cond ((null list) nil)
((atom list) (combinations (list list)))
((null (cdr list)) (cons list nil))
(t (append (cons (first list) (combinations (cdr list)))
(combinations (cdr list))))))

However, to get an iterator over the output list without actually
constructing the output list, we need some new operators. Suppose
(YIELD x) means "the value x should pop up the chain of YIELDs back to
the iterator, and when the chain ends, the result should be returned
now by the iterator," and (SEQ x y z ...) means "the iterator should
follow the sequence x, y, z if x, y, z, etc. YIELD something." Then we
could write:

(defun combinations (list)
(cond ((null list) (yield nil))
((atom list) (yield (combinations (list list))))
((null (cdr list))
(seq (yield list) (yield nil)))
(t (seq (yield (cons (first list) (combinations (cdr
list))))
(yield (combinations (cdr list)))))))

well, something like that; I'm not being very precise. The idea is
that you just write your algorithms like you would before, but instead
of collecting data into a data structure, you YIELD each datum as you
generate it. I know the SERIES package has something vaguely like the
YIELD operator; could it be (efficiently) adapted to the above case?
Or is this a job for continuations?

mfh

From: Pascal Bourguignon on
"mark.hoemmen(a)gmail.com" <mark.hoemmen(a)gmail.com> writes:

> On Jan 1, 2:47 pm, Pascal Bourguignon <p...(a)informatimago.com> wrote:
>> http://darcs.informatimago.com/darcs/public/lisp/common-lisp/combinat...
>
> what i was thinking of for the second question (generalizing the idea
> of iterator over a process) was something like this. If you want a
> list of all the combinations of the elements of a given list, you do
> something like this:
>
> (defun combinations (list)
> (cond ((null list) nil)
> ((atom list) (combinations (list list)))
> ((null (cdr list)) (cons list nil))
> (t (append (cons (first list) (combinations (cdr list)))
> (combinations (cdr list))))))
>
> However, to get an iterator over the output list without actually
> constructing the output list, we need some new operators. Suppose
> (YIELD x) means "the value x should pop up the chain of YIELDs back to
> the iterator, and when the chain ends, the result should be returned
> now by the iterator," and (SEQ x y z ...) means "the iterator should
> follow the sequence x, y, z if x, y, z, etc. YIELD something." Then we
> could write:
>
> (defun combinations (list)
> (cond ((null list) (yield nil))
> ((atom list) (yield (combinations (list list))))
> ((null (cdr list))
> (seq (yield list) (yield nil)))
> (t (seq (yield (cons (first list) (combinations (cdr
> list))))
> (yield (combinations (cdr list)))))))
>
> well, something like that; I'm not being very precise. The idea is
> that you just write your algorithms like you would before, but instead
> of collecting data into a data structure, you YIELD each datum as you
> generate it. I know the SERIES package has something vaguely like the
> YIELD operator; could it be (efficiently) adapted to the above case?
> Or is this a job for continuations?

Yes, continuations, or coroutines. It's possible that partial
continuations we can implement in CL would be enough for this.


--
__Pascal Bourguignon__ http://www.informatimago.com/

"What is this talk of "release"? Klingons do not make software
"releases". Our software "escapes" leaving a bloody trail of
designers and quality assurance people in its wake."
From: Steven E. Harris on
"mark.hoemmen(a)gmail.com" <mark.hoemmen(a)gmail.com> writes:

> I know the recursive definition of the set of all combinations of a
> set, but I'd like to avoid the combinatorial space explosion of
> actually _constructing_ the set of all combinations.

I wrote combination and permutation generators that use the
"call-with-"-style interface: One provides a function to receive each
fresh combination or permutation, which are generated one by one,
avoiding the space explosion you describe.

Such "call-with" functions may be trivially wrapped in "with-" or
"do-" macros. Again, no iterator is exposed, but no intermediate
results are built up either.

,----[ Combinations drawing 3 elements from a 5-element vector ]
| (call-with-combinations #'print 3 (vector 1 2 3 4 5))
|
| #(1 2 3)
| #(1 2 4)
| #(1 2 5)
| #(1 3 4)
| #(1 3 5)
| #(1 4 5)
| #(2 3 4)
| #(2 3 5)
| #(2 4 5)
| #(3 4 5)
`----

,----[ Permutations drawing all elements from a 4-element vector ]
| (call-with-permutations #'print (vector 4 3 2 1)) ...
|
| #(4 3 2 1)
| #(3 4 2 1)
| #(4 2 3 1)
| #(2 4 3 1)
| #(3 2 4 1)
| #(2 3 4 1)
| #(4 3 1 2)
| #(3 4 1 2)
| #(4 1 3 2)
| #(1 4 3 2)
| #(3 1 4 2)
| #(1 3 4 2)
| #(4 2 1 3)
| #(2 4 1 3)
| #(4 1 2 3)
| #(1 4 2 3)
| #(2 1 4 3)
| #(1 2 4 3)
| #(3 2 1 4)
| #(2 3 1 4)
| #(3 1 2 4)
| #(1 3 2 4)
| #(2 1 3 4)
| #(1 2 3 4)
`----

Let's add a "do-"-style wrapper:

(defmacro do-permutations ((var vector) &body body)
`(block nil
(let ((.f. (lambda (,var)
(declare (ignorable ,var))
,@body)))
(declare (dynamic-extent .f.))
(call-with-permutations .f. ,vector))))


,----[ Using "do-"-style macro wrapper ]
| (do-permutations (p (vector 4 3 2 1))
| (print p))
|
| #(4 3 2 1)
| #(3 4 2 1)
| #(4 2 3 1)
| #(2 4 3 1)
| #(3 2 4 1)
| #(2 3 4 1)
| ...
|
| (do-permutations (p (vector 4 3 2 1))
| (when (= 1 (elt p 0))
| (return "Starts with 1.")))
| "Starts with 1."
`----

Let me know if you'd like to see the code.

--
Steven E. Harris
 |  Next  |  Last
Pages: 1 2
Prev: combinations not working
Next: bad-idea-p?