23
$\begingroup$

I'm trying to build a lazy list that evaluates the n'th m-tuple or subset of a given list using Mathematicas ordering without calculating all the Tuples. The purpose is to allow for example the traversal of very large spaces of tuples without fist having to calculate the complete space of tuples. So far I have defined a function that calcluates which elements goes where for the n'th tuple:

tupleIndexes[l_, t_, n_] := Table[Mod[Quotient[n - 1, l^m] + 1, l, 1], {m, t - 1, 0, -1}] 

And have used this to define a lazy list by defining a SubValue for when Part is applied.

lazyTuple /:Part[lazyTuple[list_, t_], n_Integer] /; 0 < n <= Length[lazyTuple[list, t]]:=
   list[[tupleIndexes[Length[list], t, n]]] 

The result is that lazyTuple[{1,2,3,4},2] remains unevaluted but lazyTuple[{1,2,3,4},2][[2]] returns {1,2} as expected.

For testing I've defined a function to iterate through the list and return them all:

lazyTuple /: Length[lazyTuple[list_, t_]] := Length[list]^t
lazyTuple /: lazyTakeAll[a_lazyTuple] /; (NumericQ@Length@a) := Table[a[[i]], {i, Length@a}]

And it seems to check out:

 lazyTakeAll[lazyTuple[Range@6, 3]] == Tuples[Range@6, 3]

True

Now my question is firstly if I'm making any subtle mistakes means this isn't returning the correct order with respect to Tuples. But more importantly, how would one go about defining a similar function for lazySubsets[]

$\endgroup$
4
  • 2
    $\begingroup$ You've seen the implementation of RankSubset[] and UnrankSubset[] in Combinatorica` ? $\endgroup$ Commented Aug 17, 2012 at 15:16
  • $\begingroup$ Related: Functional style using lazy lists $\endgroup$
    – rm -rf
    Commented Aug 17, 2012 at 15:35
  • 1
    $\begingroup$ One of the questions I would ask myself first is whether you consider your lists (which you want to iterate over) as proper lists (ordered sets) - in which case the order of iteration matters, or unordered (proper) sets, in which case you simply need an iterator which would iterate through the set without a specific prescribed order (if this is the case, then insisting on the element-by-element positional correspondence to functions such as Subsets, which return entire collection at once, may be too strong a requirement). $\endgroup$ Commented Aug 17, 2012 at 16:42
  • $\begingroup$ @LeonidShifrin I wanted the iteration indexing to match in order to ensure consistency between calling lazySublistsand Sublists, however you could iterate though this in any order you wish. An added benefit of having a function that returns the iteration indices for the n'th subset is that you can for instance carry out a maximization that finds that the 42231'th subset was best, and you automatically know which set it is, without having to put in hooks to retrieve the actual subset alongside the maximization. $\endgroup$
    – jVincent
    Commented Aug 20, 2012 at 13:25

1 Answer 1

12
$\begingroup$

Well since I asked I supose I better supply at least a rudimentary answer. Turns out it was a bit harder then I initially expected to get the right order, but I think my solution is somewhat nice. What I do is, that if we want the n'th subset, I find a list of thresholds for when the first indice changes and test what threshold n is larger then. Then I continue this method on the next level, such that if we wanted the 1'th subset of 4 elements chosen in 6, the first indice is 1, and the second can be found as 1 + the 1'th subset of 3 elements chosen in 5. This way I go through each level and end up with the indices.

thresholds[n_,l_]:=Accumulate[Table[Binomial[n-i,l-1],{i,1,n-l+1}]]
compareToThreshold[n_,num_,group_]:=Position[#>=n&/@thresholds[num,group],True,1,1][[1,1]]

digDeeper[{n_,m_,l_,i_}]:=
        {n-Prepend[thresholds[m,l],0][[i]],m-i,l-1}//Append[#,Sow[compareToThreshold@@#]]&

subsetIndices[grouplength_,setlength_,nth_]:=
Accumulate[Reap[Nest[digDeeper,{nth,grouplength+1,setlength+1,1},setlength]][[2,1]]]

For my own usage I only need fixed length subsets, therefore it only works with those. For all subsets, Combinatorica's NthSubset works quite nicely. So now to make a lazySubsets, it's just a question of using the same structure as for lazyTuples in my question.

lazySubsets/:Part[lazySubsets[list_,t_],n_Integer]/;0<n<=Length[lazySubsets[list,t]] :=
  list[[subsetIndices[Length[list],t,n]]]

And to test this

lazyTakeAll[a_]/;(NumericQ@Length@a):=Table[a[[i]],{i,Length@a}]

lazyTakeAll[lazySubsets[Range@15, 7]] == Subsets[Range@15, {7}]

True

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.