Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions lib/combinat.gd
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,10 @@ DeclareGlobalFunction("Combinations");
## all combinations.
## <P/>
## <Ref Func="EnumeratorOfCombinations"/> returns an <Ref Attr="Enumerator" />
## for the same combinations.
## With second argument <A>k</A>, it is strictly sorted.
## <P/>
## The ordering of combinations from these functions can be different and also
## different from the list returned by <Ref Func="Combinations"/>.
## for the same combinations; the enumerator is equal to the strictly sorted
## list <C>Combinations( <A>mset</A>[, <A>k</A>] )</C>.
## <Ref Func="IteratorOfCombinations"/> produces them in a different order,
## without <A>k</A> by increasing size.
## <P/>
## <Example>
## gap> m:=[1..15];; Add(m, 15);
Expand All @@ -356,9 +355,9 @@ DeclareGlobalFunction("Combinations");
## 49152
## gap> cm := EnumeratorOfCombinations(m);;
## gap> cm[1000];
## [ 1, 2, 3, 6, 7, 8, 9, 10 ]
## [ 1, 2, 3, 4, 5, 7, 9, 10, 12, 13, 15, 15 ]
## gap> Position(cm, [1,13,15,15]);
## 36866
## 24572
## gap> cm := EnumeratorOfCombinations([1..30], 6);
## EnumeratorOfCombinations( [ 1 .. 30 ], 6 )
## gap> Length(cm);
Expand Down
212 changes: 156 additions & 56 deletions lib/combinat.gi
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,123 @@ BindGlobal("EnumeratorOfCombinations_k", function(mset, k)
return enum;
end);

# All combinations are numbered in lexicographic order, in which a list
# comes before its extensions. Let els = Set(mset), l = Length(els), let
# max[i] be the multiplicity of els[i] in mset, and let
# S[i] = Product(max{[i..l]} + 1) be the number of sub-multisets of mset
# with entries in els{[i..l]}. A combination with d[i] copies of els[i],
# the last nonzero one d[t], comes after its Sum_i d[i] proper prefixes
# and, for each i < t, after the (max[i] - d[i]) * S[i+1] combinations
# that agree with it below els[i] and have more copies of els[i]. For a
# set of size n, S[i] = 2^(n+1-i).
BindGlobal("ElementNumber_AllCombinations_set", function(enum, num)
local set, s, r, comb, i;
if num > enum!.size then
Error("Index ", num, " not bound, expected at most ", enum!.size);
fi;
set := enum!.set;
s := enum!.size;
r := num-1;
comb := [];
i := 0;
while r > 0 do
i := i+1;
s := QuoInt(s, 2);
# r is the rank among the combinations of set{[i..n]}: [ ], the s
# with set[i], then the s-1 nonempty ones of set{[i+1..n]}
if r <= s then
Add(comb, set[i]);
r := r-1;
else
r := r-s;
fi;
od;
return comb;
end);

BindGlobal("NumberElement_AllCombinations_set", function(enum, comb)
local set, n, num, prev, x, p;
if not IsDenseList(comb) then
return fail;
fi;
set := enum!.set;
n := Length(set);
num := enum!.size + Length(comb) + 1;
prev := 0;
# with positions p_1 < .. < p_k, the sum of 2^(n-i) over the i < p_k
# other than the p_j is 2^n - 2^(n-p_k) - Sum_j 2^(n-p_j)
for x in comb do
p := Position(set, x);
if p = fail or p <= prev then
return fail;
fi;
num := num - 2^(n-p);
prev := p;
od;
return num - 2^(n-prev);
end);

BindGlobal("ElementNumber_AllCombinations_mset", function(enum, num)
local els, max, s, r, comb, i, b;
if num > enum!.size then
Error("Index ", num, " not bound, expected at most ", enum!.size);
fi;
els := enum!.els;
max := enum!.max;
s := enum!.size;
r := num-1;
comb := [];
i := 0;
while r > 0 do
i := i+1;
s := QuoInt(s, max[i]+1);
# r is the rank among the combinations of els{[i..l]}: els[i]^a for
# a = 0 .. max[i], then for a = max[i], .., 0 the s-1 combinations of
# els[i]^a followed by a nonempty one of els{[i+1..l]}
if r <= max[i] then
Append(comb, ListWithIdenticalEntries(r, els[i]));
r := 0;
else
b := QuoInt(r-max[i]-1, s-1);
Append(comb, ListWithIdenticalEntries(max[i]-b, els[i]));
r := r - max[i] - b*(s-1);
fi;
od;
return comb;
end);

BindGlobal("NumberElement_AllCombinations_mset", function(enum, comb)
local els, max, d, prev, x, p, num, i;
if not IsDenseList(comb) then
return fail;
fi;
els := enum!.els;
max := enum!.max;
d := 0*max;
prev := 1;
for x in comb do
if x <> els[prev] then
p := Position(els, x);
if p = fail or p < prev then
return fail;
fi;
prev := p;
fi;
if d[prev] = max[prev] then
return fail;
fi;
d[prev] := d[prev]+1;
od;
# Sum_{i<t} (max[i] - d[i]) * S[i+1] by Horner's rule, with t = prev
num := 0;
for i in [1..prev-1] do
num := num * (max[i]+1) + max[i] - d[i];
od;
return num * Product(max{[prev..Length(max)]} + 1) + Length(comb) + 1;
end);

InstallGlobalFunction(EnumeratorOfCombinations, function(mset, arg...)
local c, max, l, mods, size, els, ElementNumber, NumberElement;
local els, fam, max, enum;
if Length(arg) > 1 then
Error("usage: EnumeratorOfCombinations( <mset>[, <k>] )");
elif Length(arg) = 1 then
Expand All @@ -373,61 +488,46 @@ InstallGlobalFunction(EnumeratorOfCombinations, function(mset, arg...)
fi;
return EnumeratorOfCombinations_k(mset, arg[1]);
fi;
c := Collected(mset);
max := List(c, a-> a[2]);
els := List(c, a-> a[1]);
l := Length(max);
mods := max+1;
size := Product(mods);
# a combination can contain els[i] from 0 to max[i] times (mods[i]
# possibilities), we number the combination that contains a[i] times els[i]
# for all i by n = 1 + sum_i a[i]*m[i] where m[i] = prod_(j<i) mods[i]
ElementNumber := function(enu, n)
local comb, res, i, j;
if n > size then
Error("Index ", n, " not bound.");
fi;
comb := EmptyPlist(l);
n := n-1;
for i in [1..l] do
comb[i] := n mod mods[i];
n := (n - comb[i])/mods[i];
od;
res := [];
for i in [1..l] do
for j in [1..comb[i]] do
Add(res, els[i]);
od;
od;
return res;
end;
NumberElement := function(enu, comb)
local c, d, pos, n, a, i;
if not IsList(comb) then
return fail;
fi;
c := Collected(comb);
d := 0*max;
for a in c do
pos := PositionSorted(els, a[1]);
if not IsBound(els[pos]) or els[pos] <> a[1] or a[2] > max[pos] then
return fail;
else
d[pos] := a[2];
fi;
od;
n := 0;
for i in [l,l-1..1] do
n := n*mods[i] + d[i];
od;
return n+1;
end;
return EnumeratorByFunctions(ListsFamily, rec(
ElementNumber := ElementNumber,
NumberElement := NumberElement,
els := els,
Length := x->size,
max := max));
els := Set(mset);
# [ ] and nonempty lists lie in different families
if IsEmpty(els) then
fam := FamilyObj([ [] ]);
else
fam := ListsFamily;
fi;
if Length(els) = Length(mset) then
enum := EnumeratorByFunctions(fam, rec(
ElementNumber := ElementNumber_AllCombinations_set,
NumberElement := NumberElement_AllCombinations_set,
Length := enum -> enum!.size,
PrintObj := function(enum)
Print("EnumeratorOfCombinations( ");
PrintObj(enum!.set);
Print(" )");
end,
set := els,
size := 2^Length(els)));
else
max := List(Collected(mset), a -> a[2]);
enum := EnumeratorByFunctions(fam, rec(
ElementNumber := ElementNumber_AllCombinations_mset,
NumberElement := NumberElement_AllCombinations_mset,
Length := enum -> enum!.size,
PrintObj := function(enum)
Print("EnumeratorOfCombinations( ");
PrintObj(Concatenation(List([1..Length(enum!.els)],
i -> ListWithIdenticalEntries(enum!.max[i], enum!.els[i]))));
Print(" )");
end,
els := els,
max := max,
size := Product(max+1)));
fi;
SetIsSSortedList(enum, true);
if IsRange(els) or ForAll(els, IsSmallIntRep) then
SetFilterObj(enum, IsEnumeratorOfSmallIntLists);
fi;
return enum;
end);

BindGlobal("NextIterator_Combinations_set", function(it)
Expand Down
48 changes: 48 additions & 0 deletions tst/testinstall/combinat.tst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,54 @@ gap> for n in [ 0 .. 10 ] do
> Error( "different elements" );
> fi;
> od;
gap> for mset in Concatenation( [ [ 1 .. 10 ], [ 5, 3, 1 ], "abracadabra",
> [ (1,2), (), (1,2,3) ], [ (1,2), (), (1,2), (1,2,3) ] ],
> List( [ 0 .. 12 ],
> n -> List( [ 1 .. n ], i -> Random( [ 1 .. n ] ) ) ) ) do
> comb1:= Combinations( mset );
> enum:= EnumeratorOfCombinations( mset );
> if not ( HasIsSSortedList( enum ) and IsSSortedList( enum )
> and Length( enum ) = Length( comb1 )
> and AsList( enum ) = comb1 and enum = comb1
> and List( comb1, c -> Position( enum, c ) )
> = [ 1 .. Length( comb1 ) ] ) then
> Error( "wrong enumerator for ", mset );
> fi;
> od;
gap> enum:= EnumeratorOfCombinations( [ 2, 1, 1 ] );
EnumeratorOfCombinations( [ 1, 1, 2 ] )
gap> AsList( enum );
[ [ ], [ 1 ], [ 1, 1 ], [ 1, 1, 2 ], [ 1, 2 ], [ 2 ] ]
gap> IsEnumeratorOfSmallIntLists( enum );
true
gap> List( [ [ 2, 1 ], [ 1, 1, 1 ], [ 3 ], [ 1, 1, 2, 2 ], 1 ],
> c -> Position( enum, c ) );
[ fail, fail, fail, fail, fail ]
gap> enum[ 7 ];
Error, Index 7 not bound, expected at most 6
gap> EnumeratorOfCombinations( [ 1 .. 3 ] )[ 9 ];
Error, Index 9 not bound, expected at most 8
gap> enum:= EnumeratorOfCombinations( [ 1 .. 100 ] );
EnumeratorOfCombinations( [ 1 .. 100 ] )
gap> enum[ 2^99 + 1 ];
[ 1, 100 ]
gap> enum[ 2^100 ];
[ 100 ]
gap> Position( enum, [ 2 ] ) = 2^99 + 2;
true
gap> List( [ [ 2, 1 ], [ 1, 1 ], [ 101 ], [ 1 .. 101 ], 1 ],
> c -> Position( enum, c ) );
[ fail, fail, fail, fail, fail ]
gap> mset:= Concatenation( [ 1 .. 50 ], [ 1 .. 50 ] );;
gap> enum:= EnumeratorOfCombinations( mset );;
gap> Length( enum ) = 3^50;
true
gap> enum[ 3^50 ];
[ 50, 50 ]
gap> Position( enum, [ 1, 2, 2, 50 ] ) = 3^49 + 3^48 + 2;
true
gap> enum[ 3^49 + 3^48 + 2 ];
[ 1, 2, 2, 50 ]
gap> for mset in [ [], [ 1 .. 7 ], [ 5, 3, 1 ], [ 1, 1, 2, 3, 3, 3, 4 ],
> "abracadabra", [ (1,2), (), (1,2), (1,2,3) ] ] do
> for k in [ 0 .. Length( mset ) + 1 ] do
Expand Down
Loading