From 06b848bac446eaf31c482f24605221cc91f7681c Mon Sep 17 00:00:00 2001 From: Max Horn Date: Fri, 16 Oct 2026 13:41:08 +0200 Subject: [PATCH 1/2] Add k-subset enumerator and use it for orbits on sets EnumeratorOfCombinations takes an optional k, like IteratorOfCombinations. With k it lists the combinations in lexicographic order; for a set it ranks a combination with k binomial coefficients. DomainForAction for permutations on lists of integers returned the placeholder ["BoundedTuples", [1..m], k] for OnSets, which SparseIntKey recognised by comparing strings. It now returns EnumeratorOfCombinations([1..m], k) for OnSets and EnumeratorOfTuples([1..m], k) for OnTuples and OnPairs, which had no domain and fell back to a sort dictionary. Dictionaries index these enumerators by position, or above blistlimit hash them with HashKeyBag. The old hash read the length word and dropped the last entry, so k-sets that differed only in their largest point collided. EnumeratorOfTuples no longer allocates a list in NumberElement. A position dictionary keeps its values in a list indexed by domain position, which grows to the largest position used, so a small orbit in a domain of up to 2^25 points would allocate hundreds of MB. The orbit has at most Size(G) points, so OrbitStabilizer now hashes domains with more than 8*Size(G) points, including vector spaces for matrix groups. Before: gap> G:=SymmetricGroup(100);; gap> Orbit(G,[1,2,3],OnSets);; time; 3461 gap> Orbit(G,[1,2,3],OnTuples);; time; 40960 gap> OrbitStabilizer(G,[1,2,3],OnTuples);; time; 45121 After: gap> G:=SymmetricGroup(100);; gap> Orbit(G,[1,2,3],OnSets);; time; 257 gap> Orbit(G,[1,2,3],OnTuples);; time; 1281 gap> OrbitStabilizer(G,[1,2,3],OnTuples);; time; 4294 Assisted-by: Generative AI --- doc/ref/create.xml | 20 ++-- lib/combinat.gd | 18 +++- lib/combinat.gi | 184 +++++++++++++++++++++++++++++++++-- lib/dict.gd | 4 + lib/dict.gi | 2 +- lib/dicthf.gi | 34 ++----- lib/oprt.gi | 20 +++- tst/testinstall/combinat.tst | 36 ++++++- tst/testinstall/dict.tst | 26 ++++- tst/testinstall/oprt.tst | 36 +++++++ 10 files changed, 321 insertions(+), 59 deletions(-) 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..1bd066bc5b 100644 --- a/lib/combinat.gi +++ b/lib/combinat.gi @@ -198,10 +198,173 @@ 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."); + 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."); + 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) > 0 then + return EnumeratorOfCombinations_k(mset, arg[1]); + fi; c := Collected(mset); max := List(c, a-> a[2]); els := List(c, a-> a[1]); @@ -1006,17 +1169,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 +1196,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..a7765f39e8 100644 --- a/lib/dict.gd +++ b/lib/dict.gd @@ -130,6 +130,10 @@ DeclareCategory( "IsHash", IsLookupDictionary ); ## DeclareGlobalFunction("NewDictionary"); +## default for the option blistlimit of NewDictionary: the largest domain +## that gets a position dictionary +BindGlobal("DICT_BLISTLIMIT", 2^25); + ############################################################################# ## ## <#GAPDoc Label="[2]{dict}"> diff --git a/lib/dict.gi b/lib/dict.gi index 296414c5a9..ecc35a9e0c 100644 --- a/lib/dict.gi +++ b/lib/dict.gi @@ -317,7 +317,7 @@ local hashfun,obj,dom,lookup,maxblist,forcesort; 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..dedb0d010f 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,38 @@ 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 ] #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" ); From 9184750cdefe32d71a91ea3d63450e8f4b4375cb Mon Sep 17 00:00:00 2001 From: Max Horn Date: Sun, 18 Oct 2026 01:39:46 +0200 Subject: [PATCH 2/2] Check the arguments of EnumeratorOfCombinations A k that was not a non-negative integer, such as infinity or a string, gave an empty list or an error from Binomial, and extra arguments were ignored. Both now raise an error. An index out of range now reports the length, and DICT_BLISTLIMIT says how much memory it allows. Assisted-by: Generative AI --- lib/combinat.gi | 11 ++++++++--- lib/dict.gd | 3 ++- lib/dict.gi | 2 -- tst/testinstall/combinat.tst | 12 ++++++++++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/combinat.gi b/lib/combinat.gi index 1bd066bc5b..7a9a024065 100644 --- a/lib/combinat.gi +++ b/lib/combinat.gi @@ -208,7 +208,7 @@ end); 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."); + Error("Index ", num, " not bound, expected at most ", enum!.size); fi; set := enum!.set; n := Length(set); @@ -261,7 +261,7 @@ end); 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."); + Error("Index ", num, " not bound, expected at most ", enum!.size); fi; els := enum!.els; max := enum!.max; @@ -362,7 +362,12 @@ end); InstallGlobalFunction(EnumeratorOfCombinations, function(mset, arg...) local c, max, l, mods, size, els, ElementNumber, NumberElement; - if Length(arg) > 0 then + 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); diff --git a/lib/dict.gd b/lib/dict.gd index a7765f39e8..dac66a185a 100644 --- a/lib/dict.gd +++ b/lib/dict.gd @@ -131,7 +131,8 @@ DeclareCategory( "IsHash", IsLookupDictionary ); DeclareGlobalFunction("NewDictionary"); ## default for the option blistlimit of NewDictionary: the largest domain -## that gets a position dictionary +## 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); ############################################################################# diff --git a/lib/dict.gi b/lib/dict.gi index ecc35a9e0c..a5e53f68e9 100644 --- a/lib/dict.gi +++ b/lib/dict.gi @@ -315,8 +315,6 @@ 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:=DICT_BLISTLIMIT; fi; diff --git a/tst/testinstall/combinat.tst b/tst/testinstall/combinat.tst index dedb0d010f..afc1ff896d 100644 --- a/tst/testinstall/combinat.tst +++ b/tst/testinstall/combinat.tst @@ -162,6 +162,18 @@ gap> AsList( enum ); 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( [] );