diff --git a/doc/ref/create.xml b/doc/ref/create.xml
index 507138ceba..96e9146364 100644
--- a/doc/ref/create.xml
+++ b/doc/ref/create.xml
@@ -492,19 +492,18 @@ function ( set, k )
return nn;
end,
NumberElement := function ( enum, elm )
- local n, i;
- if not IsList( elm ) then
- return fail;
- fi;
- elm := List( elm, function ( x )
- return Position( enum!.set, x );
- end );
- if fail in elm or Length( elm ) <> enum!.k then
+ local n, i, p;
+ if not IsDenseList( elm ) or Length( elm ) <> enum!.k
+ then
return fail;
fi;
n := 0;
for i in [ 1 .. enum!.k ] do
- n := Length( enum!.set ) * n + elm[i] - 1;
+ p := Position( enum!.set, elm[i] );
+ if p = fail then
+ return fail;
+ fi;
+ n := Length( enum!.set ) * n + p - 1;
od;
return n + 1;
end,
@@ -519,6 +518,9 @@ function ( set, k )
set := Set( set ),
k := k ) );
SetIsSSortedList( enum, true );
+ if IsRange( enum!.set ) or ForAll( enum!.set, IsSmallIntRep ) then
+ SetFilterObj( enum, IsEnumeratorOfSmallIntLists );
+ fi;
return enum;
end
]]>
diff --git a/lib/combinat.gd b/lib/combinat.gd
index 1115b29787..6394ca2a13 100644
--- a/lib/combinat.gd
+++ b/lib/combinat.gd
@@ -325,13 +325,13 @@ DeclareGlobalFunction("Combinations");
#############################################################################
##
#F IteratorOfCombinations( mset[, k ] )
-#F EnumeratorOfCombinations( mset )
+#F EnumeratorOfCombinations( mset[, k ] )
##
## <#GAPDoc Label="IteratorOfCombinations">
##
## Iterator and enumerator of combinations
##
-##
+##
##
##
## returns an
@@ -341,8 +341,8 @@ DeclareGlobalFunction("Combinations");
## all combinations.
##
## returns an
-## of the given multiset mset. Currently only a variant without second
-## argument k is implemented.
+## for the same combinations.
+## With second argument k, it is strictly sorted.
##
## The ordering of combinations from these functions can be different and also
## different from the list returned by .
@@ -359,6 +359,12 @@ DeclareGlobalFunction("Combinations");
## [ 1, 2, 3, 6, 7, 8, 9, 10 ]
## gap> Position(cm, [1,13,15,15]);
## 36866
+## gap> cm := EnumeratorOfCombinations([1..30], 6);
+## EnumeratorOfCombinations( [ 1 .. 30 ], 6 )
+## gap> Length(cm);
+## 593775
+## gap> Position(cm, [1,2,3,4,5,7]);
+## 2
##
##
##
@@ -639,6 +645,10 @@ DeclareGlobalFunction("Tuples");
##
DeclareGlobalFunction( "EnumeratorOfTuples" );
+## Set for the enumerators above if their elements are lists of small
+## integers, so that dictionaries can hash these lists by their contents.
+DeclareFilter( "IsEnumeratorOfSmallIntLists" );
+
#############################################################################
##
diff --git a/lib/combinat.gi b/lib/combinat.gi
index fd9424eea6..7a9a024065 100644
--- a/lib/combinat.gi
+++ b/lib/combinat.gi
@@ -198,10 +198,178 @@ end);
#############################################################################
##
#F IteratorOfCombinations( [, ] )
-#F EnumeratorOfCombinations( )
+#F EnumeratorOfCombinations( [, ] )
##
-InstallGlobalFunction(EnumeratorOfCombinations, function(mset)
+
+# The combinations with entries are numbered in lexicographic order.
+# For a set of size n, the combination at positions p_1 < ... < p_k has
+# number Binomial(n, k) - Sum_j Binomial(n - p_j, k + 1 - j), using the
+# combinatorial number system on the reflected positions n + 1 - p_j.
+BindGlobal("ElementNumber_Combinations_set", function(enum, num)
+ local set, n, r, comb, c, i, lo, hi, mid;
+ if num > enum!.size then
+ Error("Index ", num, " not bound, expected at most ", enum!.size);
+ fi;
+ set := enum!.set;
+ n := Length(set);
+ r := enum!.size - num;
+ comb := EmptyPlist(enum!.k);
+ c := n;
+ for i in [enum!.k, enum!.k-1 .. 1] do
+ # binary search for the largest lo < c with Binomial(lo, i) <= r
+ lo := i-1;
+ hi := c-1;
+ while lo < hi do
+ mid := QuoInt(lo+hi+1, 2);
+ if Binomial(mid, i) <= r then
+ lo := mid;
+ else
+ hi := mid-1;
+ fi;
+ od;
+ Add(comb, set[n-lo]);
+ r := r - Binomial(lo, i);
+ c := lo;
+ od;
+ return comb;
+end);
+
+BindGlobal("NumberElement_Combinations_set", function(enum, comb)
+ local set, n, k, num, prev, j, p;
+ k := enum!.k;
+ if not IsDenseList(comb) or Length(comb) <> k then
+ return fail;
+ fi;
+ set := enum!.set;
+ n := Length(set);
+ num := enum!.size;
+ prev := 0;
+ for j in [1..k] do
+ p := Position(set, comb[j]);
+ if p = fail or p <= prev then
+ return fail;
+ fi;
+ num := num - Binomial(n-p, k+1-j);
+ prev := p;
+ od;
+ return num;
+end);
+
+# For a multiset, nr[i][r+1] is the number of combinations with r entries
+# from els{[i..Length(els)]}. Of two combinations, the one with more copies
+# of the first element in which they differ comes first.
+BindGlobal("ElementNumber_Combinations_mset", function(enum, num)
+ local els, max, nr, r, comb, i, a;
+ if num > enum!.size then
+ Error("Index ", num, " not bound, expected at most ", enum!.size);
+ fi;
+ els := enum!.els;
+ max := enum!.max;
+ nr := enum!.nr;
+ num := num-1;
+ r := enum!.k;
+ comb := [];
+ for i in [1..Length(els)] do
+ a := Minimum(max[i], r);
+ while num >= nr[i+1][r-a+1] do
+ num := num - nr[i+1][r-a+1];
+ a := a-1;
+ od;
+ Append(comb, ListWithIdenticalEntries(a, els[i]));
+ r := r-a;
+ od;
+ return comb;
+end);
+
+BindGlobal("NumberElement_Combinations_mset", function(enum, comb)
+ local els, max, nr, d, prev, x, p, num, r, i;
+ if not IsDenseList(comb) or Length(comb) <> enum!.k then
+ return fail;
+ fi;
+ els := enum!.els;
+ max := enum!.max;
+ nr := enum!.nr;
+ d := 0*max;
+ prev := 1;
+ for x in comb do
+ p := Position(els, x);
+ if p = fail or p < prev or d[p] = max[p] then
+ return fail;
+ fi;
+ d[p] := d[p]+1;
+ prev := p;
+ od;
+ num := 1;
+ r := enum!.k;
+ for i in [1..Length(els)] do
+ num := num + Sum([d[i]+1 .. Minimum(max[i], r)], a -> nr[i+1][r-a+1]);
+ r := r-d[i];
+ od;
+ return num;
+end);
+
+BindGlobal("EnumeratorOfCombinations_k", function(mset, k)
+ local els, c, max, l, nr, i, enum;
+ if k > Length(mset) then
+ return Immutable([]);
+ fi;
+ els := Set(mset);
+ if Length(els) = Length(mset) then
+ enum := EnumeratorByFunctions(CollectionsFamily(FamilyObj(els)), rec(
+ ElementNumber := ElementNumber_Combinations_set,
+ NumberElement := NumberElement_Combinations_set,
+ Length := enum -> enum!.size,
+ PrintObj := function(enum)
+ Print("EnumeratorOfCombinations( ");
+ PrintObj(enum!.set);
+ Print(", ", enum!.k, " )");
+ end,
+ set := els,
+ k := k,
+ size := Binomial(Length(els), k)));
+ else
+ c := Collected(mset);
+ max := List(c, a -> a[2]);
+ l := Length(els);
+ nr := [];
+ nr[l+1] := Concatenation([1], ListWithIdenticalEntries(k, 0));
+ for i in [l, l-1 .. 1] do
+ nr[i] := List([0..k], r -> Sum([0..Minimum(max[i], r)],
+ a -> nr[i+1][r-a+1]));
+ od;
+ enum := EnumeratorByFunctions(CollectionsFamily(FamilyObj(els)), rec(
+ ElementNumber := ElementNumber_Combinations_mset,
+ NumberElement := NumberElement_Combinations_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(", ", enum!.k, " )");
+ end,
+ els := els,
+ max := max,
+ nr := nr,
+ k := k,
+ size := nr[1][k+1]));
+ fi;
+ SetIsSSortedList(enum, true);
+ if IsRange(els) or ForAll(els, IsSmallIntRep) then
+ SetFilterObj(enum, IsEnumeratorOfSmallIntLists);
+ fi;
+ return enum;
+end);
+
+InstallGlobalFunction(EnumeratorOfCombinations, function(mset, arg...)
local c, max, l, mods, size, els, ElementNumber, NumberElement;
+ if Length(arg) > 1 then
+ Error("usage: EnumeratorOfCombinations( [, ] )");
+ elif Length(arg) = 1 then
+ if not (IsInt(arg[1]) and arg[1] >= 0) then
+ Error("EnumeratorOfCombinations: must be a non-negative integer");
+ fi;
+ return EnumeratorOfCombinations_k(mset, arg[1]);
+ fi;
c := Collected(mset);
max := List(c, a-> a[2]);
els := List(c, a-> a[1]);
@@ -1006,17 +1174,17 @@ InstallGlobalFunction( EnumeratorOfTuples, function( set, k )
end,
NumberElement:= function( enum, elm )
- local n, i;
- if not IsList( elm ) then
- return fail;
- fi;
- elm:= List( elm, x -> Position( enum!.set, x ) );
- if fail in elm or Length( elm ) <> enum!.k then
+ local n, i, p;
+ if not IsDenseList( elm ) or Length( elm ) <> enum!.k then
return fail;
fi;
n:= 0;
for i in [ 1 .. enum!.k ] do
- n:= Length( enum!.set ) * n + elm[i] - 1;
+ p:= Position( enum!.set, elm[i] );
+ if p = fail then
+ return fail;
+ fi;
+ n:= Length( enum!.set ) * n + p - 1;
od;
return n+1;
end,
@@ -1033,6 +1201,9 @@ InstallGlobalFunction( EnumeratorOfTuples, function( set, k )
# We know that this enumerator is strictly sorted.
SetIsSSortedList( enum, true );
+ if IsRange( enum!.set ) or ForAll( enum!.set, IsSmallIntRep ) then
+ SetFilterObj( enum, IsEnumeratorOfSmallIntLists );
+ fi;
# Return the result.
return enum;
diff --git a/lib/dict.gd b/lib/dict.gd
index 9493fa6200..dac66a185a 100644
--- a/lib/dict.gd
+++ b/lib/dict.gd
@@ -130,6 +130,11 @@ DeclareCategory( "IsHash", IsLookupDictionary );
##
DeclareGlobalFunction("NewDictionary");
+## default for the option blistlimit of NewDictionary: the largest domain
+## that gets a position dictionary. Its bit list then takes 4 MB, but the
+## value list of a lookup dictionary up to 2^25 words, i.e. 256 MB.
+BindGlobal("DICT_BLISTLIMIT", 2^25);
+
#############################################################################
##
## <#GAPDoc Label="[2]{dict}">
diff --git a/lib/dict.gi b/lib/dict.gi
index 296414c5a9..a5e53f68e9 100644
--- a/lib/dict.gi
+++ b/lib/dict.gi
@@ -315,9 +315,7 @@ local hashfun,obj,dom,lookup,maxblist,forcesort;
maxblist:=ValueOption("blistlimit");
if not IsInt(maxblist) then
- #2^25 plist (for the lookup list that will hold e.g. positions)
- # is 128MB size on a 32-bit system.
- maxblist:=2^25;
+ maxblist:=DICT_BLISTLIMIT;
fi;
# are we given a domain, which can index very quickly?
diff --git a/lib/dicthf.gi b/lib/dicthf.gi
index 29c50a2a78..622e1f284f 100644
--- a/lib/dicthf.gi
+++ b/lib/dicthf.gi
@@ -148,41 +148,19 @@ end);
#############################################################################
##
-#M SparseIntKey()
+#M SparseIntKey( , )
##
-InstallMethod(SparseIntKey,"for bounded tuples",true,
- [ IsList,IsList and IsCyclotomicCollection ], 0,
+InstallMethod(SparseIntKey,"for enumerators of lists of small integers",true,
+ [ IsEnumeratorOfSmallIntLists, IsObject ], 0,
function(m, v)
- if Length(m)<> 3 or m[1]<>"BoundedTuples" then
- TryNextMethod();
- fi;
- # Due to the way BoundedTuples are presently implemented we expect the input
- # to the hash function to always be a list of positive immediate integers. This means
- # that using HashKeyWholeBag should be safe.
+ # the entries are immediate, so equal lists have equal entry slots
return function(x)
- Assert(1, IsPositionsList(x));
if not IsPlistRep(x) then
x := PlainListCopy(x);
fi;
- return HashKeyBag(x, 1, 0, Length(x) * GAPInfo.BytesPerVariable);
+ return HashKeyBag(x, 1, GAPInfo.BytesPerVariable,
+ Length(x) * GAPInfo.BytesPerVariable);
end;
-
- # alternative code w/o HashKeyBag
- ## build a weight vector to distinguish lists. Make entries large while staying clearly within
- ## immediate int (2^55 replacing 2^60, since we take subsequent primes).
- #step:=NextPrimeInt(QuoInt(2^55,Maximum(m[2])*m[3]));
- #weights:=[1];
- #len:=Length(v);
- ## up to 56 full, then increasingly reduce
- #len:=Minimum(len,8*RootInt(len));
- #while Length(weights)0 and ForAll(pnt,IsPosInt) and
ForAll(acts,IsPerm) and
- (act=OnSets or act=OnPoints or act=OnRight or act=\^)) then
+ (act=OnTuples or act=OnPairs or (act=OnSets and IsSSortedList(pnt)))) then
TryNextMethod();
fi;
m:=Maximum(Maximum(pnt),LargestMovedPoint(acts));
- # workaround to avoid creating formal objects of bounded tuples
- return ["BoundedTuples",[1..m],Length(pnt)];
+ if act=OnSets then
+ return EnumeratorOfCombinations([1..m],Length(pnt));
+ fi;
+ return EnumeratorOfTuples([1..m],Length(pnt));
end);
#############################################################################
diff --git a/tst/testinstall/combinat.tst b/tst/testinstall/combinat.tst
index 5b027dac88..afc1ff896d 100644
--- a/tst/testinstall/combinat.tst
+++ b/tst/testinstall/combinat.tst
@@ -2,7 +2,7 @@
##
## This file tests the functions that mainly deal with combinatorics.
##
-#@local n,mset,comb1,comb2,comb3,it,pn1,pn2,s,k,x,y
+#@local n,mset,comb1,comb2,comb3,it,pn1,pn2,s,k,x,y,enum
gap> START_TEST("combinat.tst");
@@ -120,7 +120,7 @@ gap> NrCombinations( [1,2,3,3,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,8], 8 );
1558
#F IteratorOfCombinations( [, ] )
-#F EnumeratorOfCombinations( )
+#F EnumeratorOfCombinations( [, ] )
gap> for n in [ 0 .. 10 ] do
> mset:= Union( [ 1 .. n ], [ 1 .. n ] );
> comb1:= Combinations( mset );
@@ -130,6 +130,50 @@ gap> for n in [ 0 .. 10 ] do
> Error( "different elements" );
> fi;
> od;
+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
+> comb1:= Set( Combinations( mset, k ) );
+> enum:= EnumeratorOfCombinations( mset, k );
+> if not ( HasIsSSortedList( enum ) and IsSSortedList( enum )
+> and Length( enum ) = Length( comb1 )
+> and AsList( enum ) = comb1
+> and List( comb1, c -> Position( enum, c ) )
+> = [ 1 .. Length( comb1 ) ] ) then
+> Error( "wrong enumerator for ", mset, " and ", k );
+> fi;
+> od;
+> od;
+gap> enum:= EnumeratorOfCombinations( [ 1 .. 30 ], 6 );
+EnumeratorOfCombinations( [ 1 .. 30 ], 6 )
+gap> Length( enum );
+593775
+gap> enum[ 300000 ];
+[ 4, 5, 7, 8, 24, 25 ]
+gap> Position( enum, last );
+300000
+gap> List( [ [ 1, 2, 3, 4, 5, 31 ], [ 1, 2, 3, 4, 6, 5 ], [ 1, 1, 2, 3, 4, 5 ],
+> [ 1, 2, 3, 4, 5 ], 1 ], c -> Position( enum, c ) );
+[ fail, fail, fail, fail, fail ]
+gap> enum:= EnumeratorOfCombinations( [ 3, 1, 1, 2, 1 ], 3 );
+EnumeratorOfCombinations( [ 1, 1, 1, 2, 3 ], 3 )
+gap> AsList( enum );
+[ [ 1, 1, 1 ], [ 1, 1, 2 ], [ 1, 1, 3 ], [ 1, 2, 3 ] ]
+gap> List( [ [ 1, 2, 1 ], [ 1, 2, 2 ], [ 1, 1, 4 ], [ 1, 1 ] ],
+> c -> Position( enum, c ) );
+[ fail, fail, fail, fail ]
+gap> enum[ 5 ];
+Error, Index 5 not bound, expected at most 4
+gap> EnumeratorOfCombinations( [ 1 .. 3 ], 2 )[ 4 ];
+Error, Index 4 not bound, expected at most 3
+gap> EnumeratorOfCombinations( [ 1 .. 3 ], -1 );
+Error, EnumeratorOfCombinations: must be a non-negative integer
+gap> EnumeratorOfCombinations( [ 1 .. 3 ], infinity );
+Error, EnumeratorOfCombinations: must be a non-negative integer
+gap> EnumeratorOfCombinations( [ 1 .. 3 ], "a" );
+Error, EnumeratorOfCombinations: must be a non-negative integer
+gap> EnumeratorOfCombinations( [ 1 .. 3 ], 1, 2 );
+Error, usage: EnumeratorOfCombinations( [, ] )
#F Arrangements( ) . . . . set of ordered combinations of a multiset
gap> Arrangements( [] );
diff --git a/tst/testinstall/dict.tst b/tst/testinstall/dict.tst
index 2b7d20f738..07123ee967 100644
--- a/tst/testinstall/dict.tst
+++ b/tst/testinstall/dict.tst
@@ -1,4 +1,4 @@
-#@local dict,i
+#@local dict,enum,f,i
gap> START_TEST("dict.tst");
gap> dict := NewDictionary(1, false);
@@ -20,4 +20,28 @@ gap> for i in [1..1000] do
> od;
gap> dict;
+
+# enumerators of combinations and tuples of small integers
+gap> enum := EnumeratorOfCombinations( [ 1 .. 10 ], 3 );;
+gap> IsPositionDictionary( NewDictionary( [ 1, 2, 3 ], true, enum ) );
+true
+gap> dict := NewDictionary( [ 1, 2, 3 ], true, enum : blistlimit := 100 );;
+gap> IsHash( dict );
+true
+gap> for i in [ 1 .. Length( enum ) ] do AddDictionary( dict, enum[i], i ); od;
+gap> ForAll( [ 1 .. Length( enum ) ], i -> LookupDictionary( dict, enum[i] ) = i );
+true
+gap> LookupDictionary( dict, [ 1, 2, 11 ] );
+fail
+gap> f := SparseIntKey( enum, [ 1, 2, 3 ] );;
+gap> Length( Set( List( enum, f ) ) );
+120
+gap> enum := EnumeratorOfTuples( [ 1 .. 5 ], 3 );;
+gap> f := SparseIntKey( enum, [ 1, 2, 3 ] );;
+gap> Length( Set( List( enum, f ) ) );
+125
+gap> f( [ 1 .. 3 ] ) = f( [ 1, 2, 3 ] );
+true
+gap> SparseIntKey( EnumeratorOfTuples( [ 2^70, 2^71 ], 2 ), [ 2^70, 2^70 ] );
+fail
gap> STOP_TEST("dict.tst");
diff --git a/tst/testinstall/oprt.tst b/tst/testinstall/oprt.tst
index 5cd3334030..4c63c8f3fb 100644
--- a/tst/testinstall/oprt.tst
+++ b/tst/testinstall/oprt.tst
@@ -97,5 +97,41 @@ Error, must act transitively on
gap> Blocks( Group( (1,2), (3,4,5) ), [1..5], [1,2] : check := false );
[ [ 1, 2 ], [ 3 ], [ 4 ], [ 5 ] ]
+# DomainForAction for permutations on lists of integers
+gap> DomainForAction( [ 1, 3 ], [ (1,2,3) ], OnSets );
+EnumeratorOfCombinations( [ 1 .. 3 ], 2 )
+gap> DomainForAction( [ 4, 1, 4 ], [ (1,2,3) ], OnTuples );
+EnumeratorOfTuples( [ 1 .. 4 ], 3 )
+gap> DomainForAction( [ 3, 1 ], [ (1,2,3) ], OnPairs );
+EnumeratorOfTuples( [ 1 .. 3 ], 2 )
+gap> DomainForAction( [ 3, 1 ], [ (1,2,3) ], OnSets );
+fail
+gap> G := SymmetricGroup( 10 );;
+gap> res := OrbitStabilizer( G, [ 2, 5, 7 ], OnSets );;
+gap> [ Length( res.orbit ), Size( res.stabilizer ) ];
+[ 120, 30240 ]
+gap> res := OrbitStabilizer( G, [ 2, 5, 7 ], OnSets : blistlimit := 10 );;
+gap> [ Length( res.orbit ), Size( res.stabilizer ) ];
+[ 120, 30240 ]
+gap> res := OrbitStabilizer( G, [ 7, 2, 7 ], OnTuples );;
+gap> [ Length( res.orbit ), Size( res.stabilizer ) ];
+[ 90, 40320 ]
+gap> res := OrbitStabilizer( G, [ 7, 2, 7 ], OnTuples : blistlimit := 10 );;
+gap> [ Length( res.orbit ), Size( res.stabilizer ) ];
+[ 90, 40320 ]
+gap> Length( Orbit( G, [ 2, 5, 7 ], OnSets ) );
+120
+
+# an orbit much smaller than its domain is hashed
+gap> SetInfoLevel( InfoHash, 1 );
+gap> res := OrbitStabilizer( Group( CycleFromList( [ 1 .. 30 ] ) ),
+> [ 1, 2, 4, 8, 16 ], OnTuples );;
+#I Hash dictionary
+gap> [ Length( res.orbit ), Size( res.stabilizer ) ];
+[ 30, 1 ]
+gap> res := OrbitStabilizer( G, [ 2, 5, 7 ], OnSets );;
+#I [ 2, 5, 7 ] Position dictionary
+gap> SetInfoLevel( InfoHash, 0 );
+
##
gap> STOP_TEST( "oprt.tst" );