how to do nested loops of variable depth?

Dana Anthony Dana.Anthony at sas.com
Mon Jan 28 20:53:32 UTC 2002


one way to do this, yes I realize this is more than a month old... I am just slow at reading my mailing lists :)

if you consider you are trying only to execute code on each combination, NOT collect them all up.

(Collection method)

cartesianDo: action

"receiver is a collection of collections.  action is a block taking as its argument an array that will contain one element of each of the collections in receiver.  for example, if you execute 
   #( (1 2) ('a' 'bc') ) cartesianDo: [ :array | Transcript show: array ]

the Transcript will show #(1 'a') #(2 'a') #(1 'bc') #(2 'bc') (order not guaranteed)"

| sizes counters step |
sizes = self collect: [ :each | each size ].
counters = self collect: [ :each | 1 ].
[ (counter at: 1) <= (sizes at: 1) ] whileFalse:
[ step = Array new.
  1 to: self size do: [ :each | step add: ((self at: each) at: (counters at: each)) ].
  action value: step.
  counters at: self size put: (counters at: self size) + 1.
  self size to: 2 step: -1 do: [ :each |
     (counters at: each) >  (sizes at: each) ifTrue:
      [counters at: each - 1 put: (counters at: each - 1) + 1.
       counters at: each put: 1 ]].
^self


This code is as typed, I didn't attempt to run it, since I don't have a working Smalltalk system installed at the moment.  It's pretty close though, I think, if not 100%.

The way it works is this:

counters keeps track of the most recent permutation provided.  it cycles like an odometer, but the click back to 1 again and upclick of the next digit over occurs at whatever the size is, rather than a fixed size.

I don't know if this is a simpler solution than using recursion, or more complicated.   it seems like it would work, though.

-Dana Anthony


-----Original Message-----
From: Baveco, Hans [mailto:J.M.Baveco at Alterra.wag-ur.nl]
Sent: Wednesday, December 19, 2001 8:20 AM
To: 'Boris_Gaertner at msg.de '; 'squeak-dev at lists.squeakfoundation.org '
Subject: RE: how to do nested loops of variable depth?


It looks like this solution is exactly what I had hoped for! Thanks!

I don't need to actually create the collection of all combinations, just
want to iterate over all possible combinations. That is for me useful in a
sort of model sensitivity-analysis: I select a variable number of parameters
and define for each parameter a range of values, and then run the model with
each possible combination of parameter values.

Thanks again,
Hans


-----Original Message-----
From: Boris_Gaertner at msg.de
To: squeak-dev at lists.squeakfoundation.org
Sent: 19-12-01 13:36
Subject: RE: how to do nested loops of variable depth?


Hello, 

the attached class (CollectionCombinator.st) 
should do what you want to do. 
To compute the cartesian product (that is what you want 
to do!) I set up an array of streams. A recursive method is 
used to build all elements of the carthesian product. 
The solution is certainly not easy to understand, but it is 
written with one design goal in mind: It does not construct 
a lot of partial results. 

A question to you: Do you really need the collection of all
combinations? 
You know that even for a small number of small collections the size of 
the cartesian product  will be huge. 

You want to process all elements of the cartesian product. OK. To do 
that, you do not need to put all these elements into a collection. 
You can process an element immediately after construction. To do that, 
you can use a block that is called once for every constructed tuple. 

Look at this: (CollectionCombinator2.st) 




A final note: 

In Smalltalks with full block closures you can do the following: 


  | result arrays sizeOfResult streams block | 


arrays := OrderedCollection new. 
arrays add: #(#a #b); 
       add: #(1 2 3 4); 
       add: #('w' 'x' 'y' 'z'). 
sizeOfResult := 
   arrays inject: 1 into: 
          [:prod :array | prod * array size]. 
streams := arrays collect: 
            [:a | ReadStream on: a]. " This is an OrderedCollection of
Streams " 

result := OrderedCollection new: sizeOfResult. 
block := 
 [:r :tupel :myIdx :allStreams | 

 [myIdx = allStreams size 
          ifTrue: [1 to: allStreams size do: 
                   [:i | tupel at: i put: (allStreams at: i) peek]. 
                         r addLast: tupel shallowCopy] 
         ifFalse:  [block  value: r value: tupel value: myIdx + 1 value:
allStreams]. 
        (allStreams at: myIdx) next.     
        (allStreams at: myIdx) atEnd 
 ] 
 whileFalse: []. 
 (allStreams at: myIdx) reset. 
   r   
  ]. 
 block value: result 
                       value: (Array new: streams size) " this is a
buffer " 
                            value: 1 
                            value: streams. 

result 


Here, a block is given a name and the block name is used 
within the block itself. This is of course a recursive call. 
When you evaluate this in a workspace, 
Squeak 3.1 will show you an error notification that states that 
you are not allowed evaluate a block that is currently evaluated. 
(In other words: You cannot use a named block as a recursive 
procedure.) In some Smalltalks you can do that. 




Cheers 
Boris Gärtner


mailto: Boris_Gaertner at msg.de
 <<CollectionCombinator.st>>  <<CollectionCombinator2.st>> 




More information about the Squeak-dev mailing list