Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/GAP/lib/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 18.9.2025 mit Größe 93 kB image not shown  

Quelle  combinat.gi   Sprache: unbekannt

 
#############################################################################
##
##  This file is part of GAP, a system for computational discrete algebra.
##  This file's authors include Martin Schönert.
##
##  Copyright of GAP belongs to its developers, whose names are too numerous
##  to list here. Please refer to the COPYRIGHT file for details.
##
##  SPDX-License-Identifier: GPL-2.0-or-later
##
##  This file contains method for combinatorics.
##


#############################################################################
##
#F  Factorial( <n> )  . . . . . . . . . . . . . . . . factorial of an integer
##
InstallGlobalFunction(Factorial, FACTORIAL_INT);


#############################################################################
##
#F  GaussianCoefficient( <n>, <k>, <q> ) . . . . . . . .  number of subspaces
##
InstallGlobalFunction(GaussianCoefficient,function ( n, k, q )
local   gc, i;
  if   k < 0 or n<0 or k>n  then
    return 0;
  else
    gc:=1;
    for i in [1..k] do
      gc:=gc*(q^(n-i+1)-1)/(q^i-1);
    od;
    return gc;
  fi;
end);


#############################################################################
##
#F  Binomial( <n>, <k> )  . . . . . . . . .  binomial coefficient of integers
##
InstallGlobalFunction(Binomial, BINOMIAL_INT);


#############################################################################
##
#F  Bell( <n> ) . . . . . . . . . . . . . . . . .  value of the Bell sequence
##
InstallGlobalFunction(Bell,function ( n )
    local   bell, k, i;
    bell := [ 1 ];
    for i  in [1..n-1]  do
        bell[i+1] := bell[1];
        for k  in [0..i-1]  do
            bell[i-k] := bell[i-k] + bell[i-k+1];
        od;
    od;
    return bell[1];
end);


#############################################################################
##
#F  Stirling1( <n>, <k> ) . . . . . . . . . Stirling number of the first kind
##
InstallGlobalFunction(Stirling1,function ( n, k )
    local   sti, i, j;
    if   n < k  then
        sti := 0;
    elif n = k  then
        sti := 1;
    elif n < 0 and k < 0  then
        sti := Stirling2( -k, -n );
    elif k <= 0  then
        sti := 0;
    else
        sti := [ 1 ];
        for j  in [2..n-k+1]  do
            sti[j] := 0;
        od;
        for i  in [1..k]  do
            sti[1] := 1;
            for j  in [2..n-k+1]  do
                sti[j] := (i+j-2) * sti[j-1] + sti[j];
            od;
        od;
        sti := sti[n-k+1];
    fi;
    return sti;
end);


#############################################################################
##
#F  Stirling2( <n>, <k> ) . . . . . . . .  Stirling number of the second kind
##
##  Uses $S_2(n,k) = (-1)^k \sum_{i=1}^{k}{(-1)^i {k \choose i} i^k} / k!$.
##
InstallGlobalFunction(Stirling2,function ( n, k )
    local   sti, bin, fib, i;
    if   n < k  then
        sti := 0;
    elif n = k  then
        sti := 1;
    elif n < 0 and k < 0  then
        sti := Stirling1( -k, -n );
    elif k <= 0  then
        sti := 0;
    else
        bin := 1;                       # (k 0)
        sti := 0;                       # (-1)^0 (k 0) 0^k
        fib := 1;                       # 0!
        for i  in [1..k]  do
            bin := (k-i+1)/i * bin;     # (k i) = (k-(i-1))/i (k i-1)
            sti := bin * i^n - sti;     # (-1)^i sum (-1)^j (k j) j^k
            fib := fib * i;             # i!
        od;
        sti := sti / fib;
    fi;
    return sti;
end);


#############################################################################
##
#F  Combinations( <mset> )  . . . . . .  set of sorted sublists of a multiset
##
##  'CombinationsA( <mset>, <m>,  <n>, <comb>, <i> )' returns  the set of all
##  combinations of the multiset <mset>, which has size  <n>, that begin with
##  '<comb>[[1..<i>-1]]'.  To do  this it finds  all elements of <mset>  that
##  can go at '<comb>[<i>]' and calls itself  recursively for each candidate.
##  <m>-1 is the position of '<comb>[<i>-1]' in <mset>, so the candidates for
##  '<comb>[<i>]' are exactly the elements 'Set( <mset>[[<m>..<n>]] )'.
##
##  'CombinationsK( <mset>, <m>, <n>, <k>, <comb>, <i>  )' returns the set of
##  all combinations  of the multiset <mset>,  which has size  <n>, that have
##  length '<i>+<k>-1', and that begin with '<comb>[[1..<i>-1]]'.  To do this
##  it finds  all elements of  <mset> that can go  at '<comb>[<i>]' and calls
##  itself recursively  for   each candidate.    <m>-1 is   the  position  of
##  '<comb>[<i>-1]'  in <mset>,  so  the  candidates  for '<comb>[<i>]'   are
##  exactly the elements 'Set( <mset>[<m>..<n>-<k>+1] )'.
##
##  'Combinations' only calls 'CombinationsA' or 'CombinationsK' with initial
##  arguments.
##
DeclareGlobalName( "CombinationsA" );
BindGlobal( "CombinationsA", function ( mset, m, n, comb, i )
    local   combs, l;
    if m = n+1  then
        comb  := ShallowCopy(comb);
        combs := [ comb ];
    else
        comb  := ShallowCopy(comb);
        combs := [ ShallowCopy(comb) ];
        for l  in [m..n]  do
            if l = m or mset[l] <> mset[l-1]  then
                comb[i] := mset[l];
                Append( combs, CombinationsA(mset,l+1,n,comb,i+1) );
            fi;
        od;
    fi;
    return combs;
end );

DeclareGlobalName( "CombinationsK" );
BindGlobal( "CombinationsK", function ( mset, m, n, k, comb, i )
    local   combs, l;
    if k = 0  then
        comb  := ShallowCopy(comb);
        combs := [ comb ];
    else
        combs := [];
        for l  in [m..n-k+1]  do
            if l = m or mset[l] <> mset[l-1]  then
                comb[i] := mset[l];
                Append( combs, CombinationsK(mset,l+1,n,k-1,comb,i+1) );
            fi;
        od;
    fi;
    return combs;
end );

InstallGlobalFunction(Combinations,function ( mset, arg... )
    local   combs;
    mset := ShallowCopy(mset);  Sort( mset );
    if Length(arg) = 0  then
        combs := CombinationsA( mset, 1, Length(mset), [], 1 );
    elif Length(arg) = 1  then
        combs := CombinationsK( mset, 1, Length(mset), arg[1], [], 1 );
    else
        Error("usage: Combinations( <mset> [, <k>] )");
    fi;
    return combs;
end);

#############################################################################
##
#F  IteratorOfCombinations( <mset>[, <k> ] )
#F  EnumeratorOfCombinations( <mset> )
##
InstallGlobalFunction(EnumeratorOfCombinations, function(mset)
  local c, max, l, mods, size, els, ElementNumber, NumberElement;
  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));
end);

BindGlobal("NextIterator_Combinations_set", function(it)
  local res, comb, k, i, len;
  comb := it!.comb;
  if comb = fail then
    Error("No more elements in iterator.");
  fi;
  # first create combination to return
  res := it!.els{comb};
  # now construct indices for next combination
  len := it!.len;
  k := it!.k;
  for i in [1..k] do
    if i = k or comb[i]+1 < comb[i+1] then
      comb[i] := comb[i] + 1;
      comb{[1..i-1]} := [1..i-1];
      break;
    fi;
  od;
  # check if done
  if k = 0 or comb[k] > len then
    it!.comb := fail;
  fi;
  return res;
end);

# helper function to substitute elements described by r!.comb[j],
# j in [1..i] by smallest possible ones
BindGlobal("Distr_Combinations", function(r, i)
  local max, kk, l, comb, j;
  max := r!.max;
  kk := 0;
  l := Length(max);
  comb := r!.comb;
  for j in [1..i] do
    kk := kk + comb[j];
    comb[j] := 0;
  od;
  for i in [1..l] do
    if kk <= max[i] then
      comb[i] := kk;
      break;
    else
      comb[i] := max[i];
      kk := kk - max[i];
    fi;
  od;
end);
BindGlobal("NextIterator_Combinations_mset", function(it)
  local res, comb, l, els, i, j, max;
  if it!.comb = fail then
    Error("No more elements in iterator.");
  fi;
  comb := it!.comb;
  max := it!.max;
  l := Length(comb);
  # first create the combination to return, this is the time critical
  # code which is more efficient in the proper set case above
  res := EmptyPlist(it!.k);
  els := it!.els;
  for i in [1..l] do
    for j in [1..comb[i]] do
      Add(res, els[i]);
    od;
  od;
  # now find next combination if there is one;
  # for this find smallest element which can be substituted by the next
  # larger element and reset the previous ones to the smallest
  # possible ones
  i := 1;
  while i < l and (comb[i] = 0 or comb[i+1] = max[i+1]) do
    i := i+1;
  od;
  if i = l then
    it!.comb := fail;
  else
    comb[i+1] := comb[i+1] + 1;
    comb[i] := comb[i] - 1;
    Distr_Combinations(it, i);
  fi;
  return res;
end);
BindGlobal("IsDoneIterator_Combinations", function(it)
  return it!.comb = fail;
end);
BindGlobal("ShallowCopy_Combinations", function(it)
  return rec(
    NextIterator := it!.NextIterator,
    IsDoneIterator := it!.IsDoneIterator,
    ShallowCopy := it!.ShallowCopy,
    els := it!.els,
    max := it!.max,
    len := it!.len,
    k := it!.k,
    comb := ShallowCopy(it!.comb));
end);
InstallGlobalFunction(IteratorOfCombinations,  function(mset, arg...)
  local k, c, max, els, len, comb, NextFunc;
  len := Length(mset);
  if Length(arg) = 0 then
    # case of one argument, call 2-arg version for each k and concatenate
    return ConcatenationIterators(List([0..len], k->
                                         IteratorOfCombinations(mset, k)));
  fi;
  k := arg[1];
  if k > len then
    return IteratorList([]);
  elif len = 0 then
    return TrivialIterator( [] );
  fi;
  c := Collected(mset);
  max := List(c, a-> a[2]);
  els := List(c, a-> a[1]);
  if Maximum(max) = 1 then
    # in case of a proper set 'mset' we use 'comb' for indices of
    # elements in current combination; this way the generation
    # of the actual combinations is a bit more efficient than below in the
    # general case of a multiset
    comb := [1..k];
    NextFunc := NextIterator_Combinations_set;
  else
    # the general case of a multiset, here 'comb'
    # describes the combination which contains comb[i] times els[i] for all i
    comb := 0*max;
    comb[1] := k;
    # initialize first combination
    Distr_Combinations(rec(comb := comb,max := max),1);
    NextFunc := NextIterator_Combinations_mset;
  fi;
  return IteratorByFunctions(rec(
    NextIterator := NextFunc,
    IsDoneIterator := IsDoneIterator_Combinations,
    ShallowCopy := ShallowCopy_Combinations,
    els := els,
    max := max,
    len := len,
    k := k,
    comb := comb));
end);


#############################################################################
##
#F  NrCombinations( <mset> )  . . . . number of sorted sublists of a multiset
##
##  'NrCombinations' just calls  'NrCombinationsSetA', 'NrCombinationsMSetA',
##  'NrCombinationsSetK' or 'NrCombinationsMSetK' depending on the arguments.
##
##  'NrCombinationsSetA' and 'NrCombinationsSetK' use well known identities.
##
##  'NrCombinationsMSetA'  and 'NrCombinationsMSetK' call  'NrCombinationsX',
##  and return either the sum or the last element of this list.
##
##  'NrCombinationsX'   returns the list  'nrs', such  that 'nrs[l+1]' is the
##  number of combinations of length l.  It uses  a recursion formula, taking
##  more and more of the elements of <mset>.
##
BindGlobal( "NrCombinationsX", function ( mset, k )
    local  nrs, nr, cnt, n, l, i;

    # count how often each element appears
    cnt := List( Collected( mset ), pair -> pair[2] );

    # there is one combination of length 0 and no other combination
    # using none of the elements
    nrs := ListWithIdenticalEntries( k+1, 0 );
    nrs[0+1] := 1;

    # take more and more elements
    for n  in [1..Length(cnt)]  do

        # loop over the possible lengths of combinations
        for l  in [k,k-1..0]  do

            # compute the number of combinations of length <l>
            # using only the first <n> elements of <mset>
            nr := 0;
            for i  in [0..Minimum(cnt[n],l)]  do

                # add the number of combinations of length <l>
                # that consist of <l>-<i> of the first <n>-1 elements
                # and <i> copies of the <n>th element
                nr := nr + nrs[l-i+1];

            od;

            nrs[l+1] := nr;

        od;

    od;

    # return the numbers
    return nrs;
end );

BindGlobal( "NrCombinationsSetA", function ( set )
    local  nr;
    nr := 2 ^ Size(set);
    return nr;
end );

BindGlobal( "NrCombinationsMSetA", function ( mset )
    local  nr;
    nr := Product( Set(mset), i->Number(mset,j->i=j)+1 );
    return nr;
end );

BindGlobal( "NrCombinationsSetK", function ( set, k )
    local  nr;
    if k <= Size(set)  then
        nr := Binomial( Size(set), k );
    else
        nr := 0;
    fi;
    return nr;
end );

BindGlobal( "NrCombinationsMSetK", function ( mset, k )
    local  nr;
    if k <= Length(mset)  then
        nr := NrCombinationsX( mset, k )[k+1];
    else
        nr := 0;
    fi;
    return nr;
end );

InstallGlobalFunction(NrCombinations,function ( mset, arg... )
    local   nr;
    mset := ShallowCopy(mset);  Sort( mset );
    if Length(arg) = 0  then
        if IsSSortedList( mset )  then
            nr := NrCombinationsSetA( mset );
        else
            nr := NrCombinationsMSetA( mset );
        fi;
    elif Length(arg) = 1  then
        if IsSSortedList( mset )  then
            nr := NrCombinationsSetK( mset, arg[1] );
        else
            nr := NrCombinationsMSetK( mset, arg[1] );
        fi;
    else
        Error("usage: NrCombinations( <mset> [, <k>] )");
    fi;
    return nr;
end);


#############################################################################
##
#F  Arrangements( <mset> )  . . . . set of ordered combinations of a multiset
##
##  'ArrangementsA( <mset>,  <m>, <n>, <comb>, <i>  )' returns the set of all
##  arrangements of the multiset <mset>, which has  size <n>, that begin with
##  '<comb>[[1..<i>-1]]'.   To do this it  finds all  elements of <mset> that
##  can go at '<comb>[<i>]' and calls itself  recursively for each candidate.
##  <m> is a boolean list of size <n> that contains  'true' for every element
##  of <mset> that we have not yet taken, so the candidates for '<comb>[<i>]'
##  are exactly the elements '<mset>[<l>]' such that '<m>[<l>]'  is  'true'.
##  Some care must be taken to take a candidate only once if it appears  more
##  than once in <mset>.
##
##  'ArrangementsK( <mset>, <m>, <n>, <k>, <comb>, <i> )'  returns the set of
##  all arrangements  of the multiset <mset>,  which has size  <n>, that have
##  length '<i>+<k>-1', and that begin with '<comb>[[1..<i>-1]]'.  To do this
##  it finds  all elements of  <mset> that can  go at '<comb>[<i>]' and calls
##  itself recursively for each candidate.  <m> is a boolean list of size <n>
##  that contains 'true' for every element  of <mset>  that we  have  not yet
##  taken,  so  the candidates for   '<comb>[<i>]' are  exactly the elements
##  '<mset>[<l>]' such that '<m>[<l>]' is 'true'.  Some care must be taken to
##  take a candidate only once if it appears more than once in <mset>.
##
##  'Arrangements' only calls 'ArrangementsA' or 'ArrangementsK' with initial
##  arguments.
##
DeclareGlobalName( "ArrangementsA" );
BindGlobal( "ArrangementsA", function ( mset, m, n, comb, i )
    local   combs, l;
    if i = n+1  then
        comb  := ShallowCopy(comb);
        combs := [ comb ];
    else
        comb  := ShallowCopy(comb);
        combs := [ ShallowCopy(comb) ];
        for l  in [1..n]  do
            if m[l] and (l=1 or m[l-1]=false or mset[l]<>mset[l-1])  then
                comb[i] := mset[l];
                m[l] := false;
                Append( combs, ArrangementsA( mset, m, n, comb, i+1 ) );
                m[l] := true;
            fi;
        od;
    fi;
    return combs;
end );

DeclareGlobalName( "ArrangementsK" );
BindGlobal( "ArrangementsK", function ( mset, m, n, k, comb, i )
    local   combs, l;
    if k = 0  then
        comb := ShallowCopy(comb);
        combs := [ comb ];
    else
        combs := [];
        for l  in [1..n]  do
            if m[l] and (l=1 or m[l-1]=false or mset[l]<>mset[l-1])  then
                comb[i] := mset[l];
                m[l] := false;
                Append( combs, ArrangementsK( mset, m, n, k-1, comb, i+1 ) );
                m[l] := true;
            fi;
        od;
    fi;
    return combs;
end );

InstallGlobalFunction(Arrangements,function ( mset, arg... )
    local   combs, m;
    mset := ShallowCopy(mset);  Sort( mset );
    m := List( mset, ReturnTrue );
    if Length(arg) = 0  then
        combs := ArrangementsA( mset, m, Length(mset), [], 1 );
    elif Length(arg) = 1  then
        combs := ArrangementsK( mset, m, Length(mset), arg[1], [], 1 );
    else
        Error("usage: Arrangements( <mset> [, <k>] )");
    fi;
    return combs;
end);


#############################################################################
##
#F  NrArrangements( <mset> )  .  number of ordered combinations of a multiset
##
##  'NrArrangements' just calls  'NrArrangementsSetA', 'NrArrangementsMSetA',
##  'NrArrangementsSetK' or 'NrArrangementsMSetK' depending on the arguments.
##
##  'NrArrangementsSetA' and 'NrArrangementsSetK' use well known identities.
##
##  'NrArrangementsMSetA'  and 'NrArrangementsMSetK' call  'NrArrangementsX',
##  and return either the sum or the last element of this list.
##
##  'NrArrangementsX'   returns the list  'nrs', such  that 'nrs[l+1]' is the
##  number of arrangements of length l.  It uses  a recursion formula, taking
##  more and more of the elements of <mset>.
##
BindGlobal( "NrArrangementsX", function ( mset, k )
    local  nrs, nr, cnt, bin, n, l, i;

    # count how often each element appears
    cnt := List( Collected( mset ), pair -> pair[2] );

    # there is one arrangement of length 0 and no other arrangement
    # using none of the elements
    nrs := ListWithIdenticalEntries( k+1, 0 );
    nrs[0+1] := 1;

    # take more and more elements
    for n  in [1..Length(cnt)]  do

        # loop over the possible lengths of arrangements
        for l  in [k,k-1..0]  do

            # compute the number of arrangements of length <l>
            # using only the first <n> elements of <mset>
            nr := 0;
            bin := 1;
            for i  in [0..Minimum(cnt[n],l)]  do

                # add the number of arrangements of length <l>
                # that consist of <l>-<i> of the first <n>-1 elements
                # and <i> copies of the <n>th element
                nr := nr + bin * nrs[l-i+1];
                bin := bin * (l-i) / (i+1);

            od;

            nrs[l+1] := nr;

        od;

    od;

    # return the numbers
    return nrs;
end );

BindGlobal( "NrArrangementsSetA", function ( set )
    local  nr, i;
    nr := 0;
    for i  in [0..Size(set)]  do
        nr := nr + Product([Size(set)-i+1..Size(set)]);
    od;
    return nr;
end );

BindGlobal( "NrArrangementsMSetA", function ( mset, k )
    local  nr;
    nr := Sum( NrArrangementsX( mset, k ) );
    return nr;
end );

BindGlobal( "NrArrangementsSetK", function ( set, k )
    local  nr;
    if k <= Size(set)  then
        nr := Product([Size(set)-k+1..Size(set)]);
    else
        nr := 0;
    fi;
    return nr;
end );

BindGlobal( "NrArrangementsMSetK", function ( mset, k )
    local  nr;
    if k <= Length(mset)  then
        nr := NrArrangementsX( mset, k )[k+1];
    else
        nr := 0;
    fi;
    return nr;
end );

InstallGlobalFunction(NrArrangements,function ( mset, arg... )
    local   nr;
    mset := ShallowCopy(mset);  Sort( mset );
    if Length(arg) = 0  then
        if IsSSortedList( mset )  then
            nr := NrArrangementsSetA( mset );
        else
            nr := NrArrangementsMSetA( mset, Length(mset) );
        fi;
    elif Length(arg) = 1  then
        if not (IsInt(arg[1]) and arg[1] >= 0) then
             Error("<k> must be a nonnegative integer");
        fi;
        if IsSSortedList( mset )  then
            nr := NrArrangementsSetK( mset, arg[1] );
        else
            nr := NrArrangementsMSetK( mset, arg[1] );
        fi;
    else
        Error("usage: NrArrangements( <mset> [, <k>] )");
    fi;
    return nr;
end);


#############################################################################
##
#F  UnorderedTuples( <set>, <k> ) . . . .  set of unordered tuples from a set
##
##  'UnorderedTuplesK( <set>, <n>, <m>, <k>, <tup>, <i> )' returns the set of
##  all unordered tuples of  the  set <set>,  which  has size <n>, that  have
##  length '<i>+<k>-1', and that begin with  '<tup>[[1..<i>-1]]'.  To do this
##  it  finds all elements  of <set>  that  can go  at '<tup>[<i>]' and calls
##  itself   recursively  for   each  candidate.  <m>  is    the  position of
##  '<tup>[<i>-1]' in <set>, so the  candidates for '<tup>[<i>]' are exactly
##  the elements '<set>[[<m>..<n>]]',  since we require that unordered tuples
##  be sorted.
##
##  'UnorderedTuples' only calls 'UnorderedTuplesK' with initial arguments.
##
DeclareGlobalName( "UnorderedTuplesK" );
BindGlobal( "UnorderedTuplesK", function ( set, n, m, k, tup, i )
    local   tups, l;
    if k = 0  then
        tup := ShallowCopy(tup);
        tups := [ tup ];
    else
        tups := [];
        for l  in [m..n]  do
            tup[i] := set[l];
            Append( tups, UnorderedTuplesK( set, n, l, k-1, tup, i+1 ) );
        od;
    fi;
    return tups;
end );

InstallGlobalFunction(UnorderedTuples,function ( set, k )
    set := Set(set);
    return UnorderedTuplesK( set, Size(set), 1, k, [], 1 );
end);


#############################################################################
##
#F  NrUnorderedTuples( <set>, <k> ) . . number unordered of tuples from a set
##
InstallGlobalFunction(NrUnorderedTuples,function ( set, k )
    return Binomial( Size(Set(set))+k-1, k );
end);


#############################################################################
##
#F  IteratorOfCartesianProduct( list1, list2, ... )
#F  IteratorOfCartesianProduct( list )
##
##  All elements of the cartesian product of lists
##  <list1>, <list2>, ... are returned in the lexicographic order.
##
BindGlobal( "IsDoneIterator_Cartesian", iter -> ( iter!.next = false ) );

BindGlobal( "NextIterator_Cartesian",
    function( iter )
    local succ, n, sets, res, i, k;
    succ := iter!.next;
    n := iter!.n;
    sets := iter!.sets;
    res := [];
    i := n;
    while i > 0 do
      res[i] := sets[i][succ[i]];
      i := i-1;
    od;

    if succ = iter!.sizes then
      iter!.next := false;
    else
      succ[n] := succ[n] + 1;
      for k in [n,n-1..2] do
        if succ[k] > iter!.sizes[k] then
          succ[k] := 1;
          succ[k-1] := succ[k-1] + 1;
        else
          break;
        fi;
      od;
    fi;

    return res;
    end);

BindGlobal( "ShallowCopy_Cartesian",
            iter -> rec(
                      sets := iter!.sets,
                     sizes := iter!.sizes,
                         n := iter!.n,
                      next := ShallowCopy( iter!.next ) ) );

BindGlobal( "IteratorOfCartesianProduct2",
    function( listsets )
    local s, n;
    if not ForAll( listsets, IsListOrCollection ) and ForAll( listsets, IsFinite ) then
      Error( "Each argument must be a finite list or collection" );
    fi;
    if ForAny( listsets, IsEmpty ) then
      return Iterator( [] );
    fi;
    s := List( listsets, Set );
    n := Length( s );
    # from now s is a list of n finite sets
    return IteratorByFunctions(
      rec( IsDoneIterator := IsDoneIterator_Cartesian,
           NextIterator   := NextIterator_Cartesian,
           ShallowCopy    := ShallowCopy_Cartesian,
           sets           := s,                      # list of sets
           sizes          := MakeImmutable( List( s, Size ) ),
           n              := n,                      # number of sets
           next           := 0 * [ 1 .. n ] + 1 ) ); # list of 1's
    end);

InstallGlobalFunction( "IteratorOfCartesianProduct",
    function( arg )
    # this mimics usage of functions Cartesian and Cartesian2
    if Length( arg ) = 1  then
        return IteratorOfCartesianProduct2( arg[1] );
    fi;
    return IteratorOfCartesianProduct2( arg );
    end);

BindGlobal( "NumberElement_Cartesian",
function(enum, x)
  local n, mults, colls, sum, pos, i;

  n:=enum!.n;
  mults:=enum!.mults;
  colls:=enum!.colls;

  if Length(x)<>n then
    return fail;
  fi;

  sum:=0;
  for i in [1..n-1] do
    pos:=Position(colls[i], x[i]);
    if pos=fail then
      return fail;
    else
      pos:=pos-1;
    fi;
    sum:=sum+pos*mults[i];
  od;

  pos:=Position(colls[n], x[n]);

  if pos=fail then
    return fail;
  fi;

  return sum+pos;
end);

BindGlobal( "ElementNumber_Cartesian",
function(enum, x)
  local n, mults, out, i, colls;

  if x>Length(enum) then
    return fail;
  fi;

  x:=x-1;

  n:=enum!.n;
  mults:=enum!.mults;
  colls:=enum!.colls;
  out:=EmptyPlist(n);

  for i in [1..n-1] do
    out[i]:=QuoInt(x, mults[i]);
    x:=x-out[i]*mults[i];
    out[i]:=colls[i][out[i]+1];
  od;
  out[n]:=colls[n][x+1];

  return out;
end);

BindGlobal( "EnumeratorOfCartesianProduct2",
  function(colls)
  local new_colls, mults, k, out, i, j;

    if (not ForAll(colls, IsFinite)) or not (ForAll(colls, IsCollection) or
     ForAll(colls, IsEnumeratorByFunctions)) then
      ErrorNoReturn("usage: each argument must be a finite collection or enumerator,");
    fi;

    new_colls:=[];
    for i in [1..Length(colls)] do
      if IsDomain(colls[i]) then
        new_colls[i]:=Enumerator(colls[i]);
      else
        new_colls[i]:=colls[i];
      fi;
    od;

    mults:=List(new_colls, Length);
    for i in [1..Length(new_colls)-1] do
      k:=1;
      for j in [i+1..Length(new_colls)] do
        k:=k*Length(new_colls[j]);
      od;
      mults[i]:=k;
    od;
    mults[Length(new_colls)]:=0;

    out:=EnumeratorByFunctions(ListsFamily,
      rec( NumberElement := NumberElement_Cartesian,
           ElementNumber := ElementNumber_Cartesian,
           mults:=mults,
           n:=Length(colls),
           colls:=new_colls,
           Length:=enum-> Maximum([mults[1],1])*Length(new_colls[1])));
    SetIsFinite(out, true);
    return out;
end);

InstallGlobalFunction( "EnumeratorOfCartesianProduct",
    function( arg )
    # this mimics usage of functions Cartesian and Cartesian2
    if IsEmpty(arg) or ForAny(arg, IsEmpty) then
      return [];
    elif Length( arg ) = 1  then
        return EnumeratorOfCartesianProduct2( arg[1] );
    fi;
    return EnumeratorOfCartesianProduct2( arg );
end);

#############################################################################
##
#F  Tuples( <set>, <k> )  . . . . . . . . .  set of ordered tuples from a set
##
##  'TuplesK( <set>, <k>, <tup>, <i> )' returns the set  of all tuples of the
##  set   <set>   that have   length     '<i>+<k>-1',  and  that  begin  with
##  '<tup>[[1..<i>-1]]'.  To do this  it loops  over  all elements of  <set>,
##  puts them at '<tup>[<i>]' and calls itself recursively.
##
##  'Tuples' only calls 'TuplesK' with initial arguments.
##
DeclareGlobalName( "TuplesK" );
BindGlobal( "TuplesK", function ( set, k, tup, i )
    local   tups, l;
    if k = 0  then
        tup := ShallowCopy(tup);
        tups := [ tup ];
    else
        tups := [];
        for l  in set  do
            tup[i] := l;
            Append( tups, TuplesK( set, k-1, tup, i+1 ) );
        od;
    fi;
    return tups;
end );

InstallGlobalFunction(Tuples,function ( set, k )
    set := Set(set);
    return TuplesK( set, k, [], 1 );
end);


#############################################################################
##
#F  EnumeratorOfTuples( <set>, <k> )
##
InstallGlobalFunction( EnumeratorOfTuples, function( set, k )
    local enum;

    # Handle some trivial cases first.
    if k = 0 then
      return Immutable( [ [] ] );
    elif IsEmpty( set ) then
      return Immutable( [] );
    fi;

    # Construct the object.
    enum:= EnumeratorByFunctions( CollectionsFamily( FamilyObj( set ) ), rec(
        # Add the functions.
        ElementNumber:= function( enum, n )
          local nn, t, i;
          nn:= n-1;
          t:= [];
          for i in [ 1 .. enum!.k ] do
            t[i]:= RemInt( nn, Length( enum!.set ) ) + 1;
            nn:= QuoInt( nn, Length( enum!.set ) );
          od;
          if nn <> 0 then
            Error( "<enum>[", n, "] must have an assigned value" );
          fi;
          nn:= enum!.set{ Reversed( t ) };
          MakeImmutable( nn );
          return nn;
        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
            return fail;
          fi;
          n:= 0;
          for i in [ 1 .. enum!.k ] do
            n:= Length( enum!.set ) * n + elm[i] - 1;
          od;
          return n+1;
        end,

        Length:= enum -> Length( enum!.set )^enum!.k,

        PrintObj:= function( enum )
          Print( "EnumeratorOfTuples( ", enum!.set, ", ", enum!.k, " )" );
        end,

        # Add the data.
        set:= Set( set ),
        k:= k ) );

    # We know that this enumerator is strictly sorted.
    SetIsSSortedList( enum, true );

    # Return the result.
    return enum;
    end );


#############################################################################
##
#F  IteratorOfTuples( <set>, <n> )
##
##  All ordered tuples of length <n> of the set <set>
##  are returned in lexicographic order.
##
BindGlobal( "IsDoneIterator_Tuples", iter -> ( iter!.next = false ) );

BindGlobal( "NextIterator_Tuples", function( iter )
    local t, m, n, succ, k;

    t := iter!.next;
    m := iter!.m;
    n := iter!.n;

    if t = iter!.last then
      succ := false;
    else
      succ := ShallowCopy( t );
      succ[n] := succ[n] + 1;
      for k in [n,n-1..2] do
        if succ[k] > m then
          succ[k] := succ[k] - m;
          succ[k-1] := succ[k-1] + 1;
        else
          break;
        fi;
      od;
    fi;

    iter!.next:= succ;
    return iter!.set{t};
    end );

BindGlobal( "ShallowCopy_Tuples",
    iter -> rec( m    := iter!.m,
                 n    := iter!.n,
                 last := iter!.last,
                 set  := iter!.set,
                 next := ShallowCopy( iter!.next ) ) );

InstallGlobalFunction( "IteratorOfTuples",
    function( s, n )

    if not ( n=0 or IsPosInt( n ) ) then
        Error( "The second argument <n> must be a non-negative integer" );
    fi;

    if not ( IsCollection( s ) and IsFinite( s ) or IsEmpty( s ) and n=0 ) then
        if s = [] then
            return IteratorByFunctions(
              rec( IsDoneIterator := ReturnTrue,
                   NextIterator   := NextIterator_Tuples,
                   ShallowCopy    := ShallowCopy_Tuples,
                             next := false) );
        else
            Error( "The first argument <s> must be a finite collection or empty" );
        fi;
    fi;
    s := Set(s);
    # from now on s is a finite set and n is its Cartesian power to be enumerated
    return IteratorByFunctions(
      rec( IsDoneIterator := IsDoneIterator_Tuples,
           NextIterator   := NextIterator_Tuples,
           ShallowCopy    := ShallowCopy_Tuples,
           set            := s,
           m              := Size(s),
           last           := 0 * [1..n] + ~!.m,
           n              := n,
           next           := 0 * [ 1 .. n ] + 1 ) );
    end );


#############################################################################
##
#F  NrTuples( <set>, <k> )  . . . . . . . number of ordered tuples from a set
##
InstallGlobalFunction(NrTuples,function ( set, k )
    return Size(Set(set)) ^ k;
end);


#############################################################################
##
#F  PermutationsList( <mset> )  . . . . . . set of permutations of a multiset
##
##  'PermutationsListK( <mset>, <m>, <n>, <k>, <perm>, <i> )' returns the set
##  of all  permutations  of the multiset  <mset>, which  has  size <n>, that
##  begin  with '<perm>[[1..<i>-1]]'.  To do   this it finds all elements  of
##  <mset> that can go at '<perm>[<i>]' and calls itself recursively for each
##  candidate.  <m> is a  boolean  list of size  <n> that contains 'true' for
##  every element of <mset> that we have not yet taken, so the candidates for
##  '<perm>[<i>]'  are    exactly the   elements   '<mset>[<l>]' such   that
##  '<m>[<l>]' is 'true'.  Some care must be taken to take a  candidate  only
##  once if it appears more than once in <mset>.
##
##  'Permutations' only calls 'PermutationsListK' with initial arguments.
##
DeclareGlobalName( "PermutationsListK" );
BindGlobal( "PermutationsListK", function ( mset, m, n, k, perm, i )
    local   perms, l;
    if k = 0  then
        perm := ShallowCopy(perm);
        perms := [ perm ];
    else
        perms := [];
        for l  in [1..n]  do
            if m[l] and (l=1 or m[l-1]=false or mset[l]<>mset[l-1])  then
                perm[i] := mset[l];
                m[l] := false;
                Append( perms, PermutationsListK(mset,m,n,k-1,perm,i+1) );
                m[l] := true;
            fi;
        od;
    fi;
    return perms;
end );

InstallGlobalFunction(PermutationsList,function ( mset )
    local   m;
    mset := ShallowCopy(mset);  Sort( mset );
    m := List( mset, ReturnTrue );
    return PermutationsListK(mset,m,Length(mset),Length(mset),[],1);
end);


#############################################################################
##
#F  NrPermutationsList( <mset> )  . . .  number of permutations of a multiset
##
##  'NrPermutationsList' uses the well known multinomial coefficient formula.
##
InstallGlobalFunction(NrPermutationsList,function ( mset )
    local   nr, m;
    nr := Factorial( Length(mset) );
    for m  in Set(mset)  do
        nr := nr / Factorial( Number( mset, i->i = m ) );
    od;
    return nr;
end);


#############################################################################
##
#F  Derangements( <list> ) . . . . set of fixpointfree permutations of a list
##
##  'DerangementsK( <mset>, <m>, <n>, <list>, <k>, <perm>, <i> )' returns the
##  set of all permutations of the multiset <mset>, which  has size <n>, that
##  have no element  at the  same position  as <list>,  and that begin   with
##  '<perm>[[1..<i>-1]]'.   To do this it finds  all elements  of <mset> that
##  can go at '<perm>[<i>]' and calls itself  recursively for each candidate.
##  <m> is a boolean list of size <n> that contains  'true' for every element
##  that we have not  yet taken, so the  candidates for '<perm>[<i>]' are the
##  elements '<mset>[<l>]' such that '<m>[<l>]' is 'true'.  Some care must be
##  taken  to  take a candidate   only once if  it  append more  than once in
##  <mset>.
##
DeclareGlobalName( "DerangementsK" );
BindGlobal( "DerangementsK", function ( mset, m, n, list, k, perm, i )
    local   perms, l;
    if k = 0  then
        perm := ShallowCopy(perm);
        perms := [ perm ];
    else
        perms := [];
        for l  in [1..n]  do
            if m[l] and (l=1 or m[l-1]=false or mset[l]<>mset[l-1])
              and mset[l] <> list[i]  then
                perm[i] := mset[l];
                m[l] := false;
                Append( perms, DerangementsK(mset,m,n,list,k-1,perm,i+1) );
                m[l] := true;
            fi;
        od;
    fi;
    return perms;
end );

InstallGlobalFunction(Derangements,function ( list )
    local   mset, m;
    mset := ShallowCopy(list);  Sort( mset );
    m := List( mset, ReturnTrue );
    return DerangementsK(mset,m,Length(mset),list,Length(mset),[],1);
end);


#############################################################################
##
#F  NrDerangements( <list> ) .  number of fixpointfree permutations of a list
##
##  'NrDerangements' uses well known  identities if <mset>  is a proper  set.
##  If <mset> is a multiset it  uses 'NrDerangementsK', which works just like
##  'DerangementsK'.
##
DeclareGlobalName( "NrDerangementsK" );
BindGlobal( "NrDerangementsK", function ( mset, m, n, list, k, i )
    local   perms, l;
    if k = 0  then
        perms := 1;
    else
        perms := 0;
        for l  in [1..n]  do
            if m[l] and (l=1 or m[l-1]=false or mset[l]<>mset[l-1])
              and mset[l] <> list[i]  then
                m[l] := false;
                perms := perms + NrDerangementsK(mset,m,n,list,k-1,i+1);
                m[l] := true;
            fi;
        od;
    fi;
    return perms;
end );

InstallGlobalFunction(NrDerangements,function ( list )
    local   nr, mset, m, i;
    mset := ShallowCopy(list);  Sort( mset );
    if IsSSortedList(mset)  then
        if Size(mset) = 0  then
            nr := 1;
        elif Size(mset) = 1  then
            nr := 0;
        else
            m := - Factorial(Size(mset));
            nr := 0;
            for i  in [2..Size(mset)]  do
                m := - m / i;
                nr := nr + m;
            od;
        fi;
    else
        m := List( mset, ReturnTrue );
        nr := NrDerangementsK(mset,m,Length(mset),list,Length(mset),1);
    fi;
    return nr;
end);


#############################################################################
##
#F  Permanent( <mat> )  . . . . . . . . . . . . . . . . permanent of a matrix
##
DeclareGlobalName( "Permanent2" );
BindGlobal( "Permanent2", function ( mat, m, n, r, v, i, sum )
    local   p,  k;
    if i = n+1  then
        p := v;
        for k  in sum  do p := p * k;  od;
    else
        p := Permanent2( mat, m, n, r, v, i+1, sum )
             + Permanent2( mat, m, n, r+1, v*(r-m)/(n-r), i+1, sum+mat[i] );
    fi;
    return p;
end );

InstallMethod(Permanent,
   "for matrices",
   [ IsMatrix ],
function ( mat )
    local m, n;

    m := NrRows(mat);
    n := NrCols(mat);
    while n<m do
        Error("Matrix may not have fewer columns than rows");
    od;
    mat := TransposedMat(mat);
    return Permanent2( mat, m, n, 0, (-1)^m*Binomial(n,m), 1, 0*mat[1] );
end);


#############################################################################
##
#F  PartitionsSet( <set> )  . . . . . . . . . . .  set of partitions of a set
##
##  'PartitionsSetA( <set>,  <n>, <m>, <o>, <part>,  <i>, <j> )'  returns the
##  set  of all partitions of  the set <set>, which  has size <n>, that begin
##  with  '<part>[[1..<i>-1]]'  and   where the    <i>-th   set begins   with
##  '<part>[<i>][[1..<j>]]'.    To do so  it  does two things.   It finds all
##  elements of  <mset> that can  go at '<part>[<i>][<j>+1]' and calls itself
##  recursively for  each candidate.  And it  considers the set '<part>[<i>]'
##  to be complete  and starts a  new  set '<part>[<i>+1]', which must  start
##  with the smallest element of <mset> not yet taken, because we require the
##  returned partitions to be  sorted lexicographically.  <mset> is a boolean
##  list that contains 'true' for every element of <mset> not yet taken.  <o>
##  is the position  of '<part>[<i>][<j>]' in  <mset>, so the candidates  for
##  '<part>[<i>][<j>+1]' are  those elements '<mset>[<l>]'  for  which '<o> <
##  <l>' and '<m>[<l>]' is 'true'.
##
##  'PartitionsSetK( <set>, <n>,  <m>, <o>, <k>,  <part>, <i>, <j> )' returns
##  the set of all partitions of the set <set>, which has size <n>, that have
##  '<k>+<i>-1' subsets, and  that begin with '<part>[[1..<i>-1]]'  and where
##  the <i>-th set begins with '<part>[<i>][[1..<j>]]'.  To do so it does two
##  things.  It   finds    all  elements   of   <mset>    that  can   go   at
##  '<part>[<i>][<j>+1]'  and calls  itself  recursively for  each candidate.
##  And,  if <k> is  larger than 1, it considers  the set '<part>[<i>]' to be
##  complete and starts a new set '<part>[<i>+1]',  which must start with the
##  smallest element of <mset> not yet taken, because we require the returned
##  partitions to be sorted lexicographically.  <mset> is a boolean list that
##  contains 'true' for every element  of <mset> not yet  taken.  <o> is  the
##  position    of '<part>[<i>][<j>]'  in    <mset>,  so  the  candidates for
##  '<part>[<i>][<j>+1]' are those elements  '<mset>[<l>]'  for which '<o>  <
##  <l>' and '<m>[<l>]' is 'true'.
##
##  'PartitionsSet' only  calls   'PartitionsSetA' or  'PartitionsSetK'  with
##  initial arguments.
##
DeclareGlobalName( "PartitionsSetA" );
BindGlobal( "PartitionsSetA", function ( set, n, m, o, part, i, j )
    local   parts, npart, l;
    l := Position(m,true);
    if l = fail  then
        part := List(part,ShallowCopy);
        parts := [ part ];
    else
        npart := ShallowCopy(part);
        m[l] := false;
        npart[i+1] := [ set[l] ];
        parts := PartitionsSetA(set,n,m,l+1,npart,i+1,1);
        m[l] := true;
        part := ShallowCopy(part);
        part[i] := ShallowCopy(part[i]);
        for l  in [o..n]  do
            if m[l]   then
                m[l] := false;
                part[i][j+1] := set[l];
                Append( parts, PartitionsSetA(set,n,m,l+1,part,i,j+1));
                m[l] := true;
            fi;
        od;
    fi;
    return parts;
end );

DeclareGlobalName( "PartitionsSetK" );
BindGlobal( "PartitionsSetK", function ( set, n, m, o, k, part, i, j )
    local   parts, npart, l;
    l := Position(m,true);
    parts := [];
    if k = 1  then
        part := List(part,ShallowCopy);
        for l  in [k..n]  do
            if m[l]  then
                Add( part[i], set[l] );
            fi;
        od;
        parts := [ part ];
    elif l <> fail  then
        npart := ShallowCopy(part);
        m[l] := false;
        npart[i+1] := [ set[l] ];
        parts := PartitionsSetK(set,n,m,l+1,k-1,npart,i+1,1);
        m[l] := true;
        part := ShallowCopy(part);
        part[i] := ShallowCopy(part[i]);
        for l  in [o..n]  do
            if m[l]  then
                m[l] := false;
                part[i][j+1] := set[l];
                Append( parts, PartitionsSetK(set,n,m,l+1,k,part,i,j+1));
                m[l] := true;
            fi;
        od;
    fi;
    return parts;
end );

InstallGlobalFunction(PartitionsSet,function ( set, arg... )
    local   parts, m, k;
    if not IsSSortedList(set)  then
        Error("PartitionsSet: <set> must be a set");
    fi;
    if Length(arg) = 0  then
        if set = []  then
            parts := [ [  ] ];
        else
            m := List( set, ReturnTrue );
            m[1] := false;
            parts := PartitionsSetA(set,Length(set),m,2,[[set[1]]],1,1);
        fi;
    elif Length(arg) = 1  then
        k := arg[1];
        if set = []  then
            if k = 0  then
                parts := [ [ ] ];
            else
                parts := [ ];
            fi;
        else
            m := List( set, ReturnTrue );
            m[1] := false;
            parts := PartitionsSetK(
                        set, Length(set), m, 2, k, [[set[1]]], 1, 1 );
        fi;
    else
        Error("usage: PartitionsSet( <n> [, <k>] )");
    fi;
    return parts;
end);


#############################################################################
##
#F  NrPartitionsSet( <set> )  . . . . . . . . . number of partitions of a set
##
InstallGlobalFunction(NrPartitionsSet,function ( set, arg... )
    local   nr;
    if not IsSSortedList(set)  then
        Error("NrPartitionsSet: <set> must be a set");
    fi;
    if Length(arg) = 0  then
        nr := Bell( Size(set) );
    elif Length(arg) = 1  then
        nr := Stirling2( Size(set), arg[1] );
    else
        Error("usage: NrPartitionsSet( <n> [, <k>] )");
    fi;
    return nr;
end);


#############################################################################
##
#F  Partitions( <n> ) . . . . . . . . . . . . set of partitions of an integer
##
##  'PartitionsA( <n>, <m>, <part>, <i> )' returns the  set of all partitions
##  of '<n> +  Sum(<part>[[1..<i>-1]])' that begin with '<part>[[1..<i>-1]]'.
##  To do so  it finds  all values that  can go  at  '<part>[<i>]' and  calls
##  itself recursively for each   candidate.  <m> is '<part>[<i>-1]', so  the
##  candidates  for   '<part>[<i>]'  are '[1..Minimum(<m>,<n>)]',   since  we
##  require that partitions are nonincreasing.
##
##  There is one hack  that needs some comments.   Each call to 'PartitionsA'
##  contributes one  partition  without   going into recursion,    namely the
##  'Concatenation(  <part>[[1..<i>-1]], [1,1,...,1]  )'.  Of all  partitions
##  returned by 'PartitionsA'  this  is the smallest,  i.e.,  it will be  the
##  first  one in the result  set.   Therefore it is  put  into the result set
##  before anything else is done.  However it  is not immediately padded with
##  1, this is  the last  thing  'PartitionsA' does before  returning.  In the
##  meantime the  list is  used as a   temporary that is passed  to recursive
##  invocations.  Note that the fact that each call contributes one partition
##  without going into recursion means that  the number of recursive calls to
##  'PartitionsA'  (and the number  of  calls to  'ShallowCopy') is equal  to
##  'NrPartitions(<n>)'.
##
##  'PartitionsK( <n>,  <m>,  <k>, <part>,  <i>  )' returns  the  set of  all
##  partitions    of  '<n>  +   Sum(<part>[[1..<i>-1]])'  that    have length
##  '<k>+<i>-1' and that begin with '<part>[[1..<i>-1]]'.   To do so it finds
##  all values that can go at '<part>[<i>]' and  calls itself recursively for
##  each  candidate.    <m>  is  '<part>[<i>-1]',   so  the   candidates  for
##  '<part>[<i>]' must be  less than or  equal to <m>,  since we require that
##  partitions    are  nonincreasing.   Also    '<part>[<i>]' must    be  \<=
##  '<n>+1-<k>', since  we need at   least  <k>-1  ones  to  fill the   <k>-1
##  positions of <part> remaining after  filling '<part>[<i>]'.  On the other
##  hand '<part>[<i>]'  must be  >=  '<n>/<k>', because otherwise  we  cannot
##  fill the <k>-1 remaining positions nonincreasingly.   It is not difficult
##  to show  that for  each  candidate satisfying these properties   there is
##  indeed a partition, i.e., we never run into a dead end.
##
##  'Partitions'  only  calls  'PartitionsA'  or  'PartitionsK'  with initial
##  arguments.
##
DeclareGlobalName( "PartitionsA" );
BindGlobal( "PartitionsA", function ( n, m, part, i )
    local   parts, l;
    if n = 0  then
        part := ShallowCopy(part);
        parts := [ part ];
    elif n <= m  then
        part := ShallowCopy(part);
        parts := [ part ];
        for l  in [2..n]  do
            part[i] := l;
            Append( parts, PartitionsA( n-l, l, part, i+1 ) );
        od;
        for l  in [i..i+n-1]  do
            part[l] := 1;
        od;
    else
        part := ShallowCopy(part);
        parts := [ part ];
        for l  in [2..m]  do
            part[i] := l;
            Append( parts, PartitionsA( n-l, l, part, i+1 ) );
        od;
        for l  in [i..i+n-1]  do
            part[l] := 1;
        od;
    fi;
    return parts;
end );

DeclareGlobalName( "PartitionsK" );
BindGlobal( "PartitionsK", function ( n, m, k, part, i )
    local   parts, l;
    if k = 1  then
        part := ShallowCopy(part);
        part[i] := n;
        parts := [ part ];
    elif n+1-k < m  then
        parts := [];
        for l  in [QuoInt(n+k-1,k)..n+1-k]  do
            part[i] := l;
            Append( parts, PartitionsK( n-l, l, k-1, part, i+1 ) );
        od;
    else
        parts := [];
        for l  in [QuoInt(n+k-1,k)..m]  do
            part[i] := l;
            Append( parts, PartitionsK( n-l, l, k-1, part, i+1 ) );
        od;
    fi;
    return parts;
end );

# The following used to be `Partitions' but was renamed, because
# the new `Partitions' is much faster and produces less garbage, see
# below.
InstallGlobalFunction(PartitionsRecursively,function ( n, arg... )
    local   parts, k;
    if Length(arg) = 0  then
        parts := PartitionsA( n, n, [], 1 );
    elif Length(arg) = 1  then
        k := arg[1];
        if n = 0  then
            if k = 0  then
                parts := [ [  ] ];
            else
                parts := [  ];
            fi;
        else
            if k = 0  then
                parts := [  ];
            else
                parts := PartitionsK( n, n, k, [], 1 );
            fi;
        fi;
    else
        Error("usage: Partitions( <n> [, <k>] )");
    fi;
    return parts;
end);


BindGlobal( "GPartitionsEasy", function(n)
  # Returns a list of all Partitions of n, sorted lexicographically.
  # Algorithm/Proof: Let P_n be the set of partitions of n.
  # Let B_n^k be the set of partitions of n with all parts less or equal to k.
  # Then P_n := Union_{k=1}^n [k] + B_{n-k}^k, where "[k]+" means, that
  # a part k is added. Note that the union is a disjoint union.
  # The algorithm first enumerates B_{n-k}^k for k=1,2,...,n-1 and then
  # puts everything together by adding the greatest part.
  # The GAP list B has as its j'th entry B[j] := B_{n-j}^j for j=1,...,n-1.
  # Note the greatest part of all partitions in all of B is less than or
  # equal to QuoInt(n,2).
  # The first stage of the algorithm consists of a loop, where k runs
  # from 1 to QuoInt(n,2) and for each k all partitions are added to all
  # B[j] with greatest part k. Because we run j in descending direction,
  # we already have B[j+k] (partitions of n-j-k) ready up to greatest part k
  # when we handle for B[j] (partitions of n-j) the partitions with greatest
  # part k.
  # In the second stage we only have to add the correct greatest part to get
  # a partition of n.
  # Note that `GPartitions' improves this by including the work for the
  # second step in the first one, such that less garbage objects are generated.
  # n must be a natural number >= 1.
  local B,j,k,l,p,res;
  B := List([1..n-1],x->[]);
  for k in [1..QuoInt(n,2)] do
    # Now we add all partitions for all entries of B with greatest part k.
    Add(B[n-k],[k]);   # the trivial partition with greatest part k
    for j in [n-k-1,n-k-2..k] do
      # exactly in those are partitions with greatest part k. Think!
      # we handle B[j] (partitions of n-j) with greatest part k
      for p in B[j+k] do    # those are partitions of n-j-k
        l := [k];
        Append(l,p);    # This prolonges the bag without creating garbage!
        Add(B[j],l);
      od;
    od;
  od;
  res := [];    # here we collect the result
  for k in [1..n-1] do   # handle partitions with greatest part k
    for p in B[k] do     # use B[k] = B_{n-k}^k
      l := [k];          # add a part k
      Append(l,p);
      Add(res,l);        # collect
    od;
  od;
  Add(res,[n]);    # one more case
  return res;
end );

BindGlobal( "GPartitions", function(n)
  # Returns a list of all Partitions of n, sorted lexicographically.
  # Algorithm/Proof: See first the comment of `GPartitionsEasy'.
  # This function does exactly the same as `GPartitionsEasy' by the same
  # algorithm, but it produces nearly no garbage, because in contrast
  # to `GPartitionsEasy' the greatest part added in the second stage is
  # already added in the first stage.
  # n must be a natural number >= 1.
  local B,j,k,l,p;
  B := List([1..n],x->[]);
  for k in [1..QuoInt(n,2)] do
    # Now we add all partitions for all entries of B with greatest part k.
    Add(B[n-k],[n-k,k]);   # the trivial partition with greatest part k
    for j in [n-k-1,n-k-2..k] do
      # exactly in those are partitions with greatest part k. Think!
      # we handle B[j] (partitions of n-j) with greatest part k
      for p in B[j+k] do    # those are partitions of n-j-k
        l := [j];       # This is the greatest part for stage 2
        Append(l,p);    # This prolonges the bag without creating garbage!
        l[2] := k;      # here used to be the greatest part for stage 2, now k
        Add(B[j],l);
      od;
    od;
  od;
  B[n][1] := [n];       # one more case
  return Concatenation(B);
end );

BindGlobal( "GPartitionsNrPartsHelper", function(n,m,ones)
  # Helper function for GPartitionsNrParts (see below) for the case
  # m > n. This is used only internally if m > QuoInt(n,2), because then
  # the standard routine does not work. Here we just calculate all partitions
  # of n and append a part m to it. We use exactly the algorithm in
  # `GPartitions'.
  local B,j,k,p,res;
  B := List([1..n-1],x->[]);
  for k in [1..QuoInt(n,2)] do
    # Now we add all partitions for all entries of B with greatest part k.
    Add(B[n-k],ones[m]+ones[k]);   # the trivial partition with greatest part k
    for j in [n-k-1,n-k-2..k] do
      # exactly in those are partitions with greatest part k. Think!
      # we handle B[j] (partitions of n-j) with greatest part k
      for p in B[j+k] do    # those are partitions of n-j-k
        Add(B[j],p + ones[k]);
      od;
    od;
  od;
  res := [];    # here we collect the result
  for k in [1..n-1] do   # handle partitions with greatest part k
    for p in B[k] do     # use B[k] = B_{n-k}^k
      AddRowVector(p,ones[k]);
      Add(res,p);        # collect
    od;
  od;
  Add(res,ones[m]+ones[n]);    # one more case
  return res;
end );


BindGlobal( "GPartitionsNrParts", function(n,m)
  # This function enumerates the set of all partitions of <n> into exactly
  # <m> parts.
  # We call a partition "admissible", if
  #  0) the sum s of its entries is <= n
  #  1) it has less or equal to m parts
  #  2) let g be its greatest part and k the number of parts,
  #     (m-k)*g+s <= n
  #     [this means that it may eventually lead to a partition of n with
  #      exactly m parts]
  # We proceed in steps. In the first step we write down all admissible
  # partitions with exactly 1 part, sorted by their greatest part.
  # In the t-th step (t from 2 to m-2) we use the partitions from step
  # t-1 to enumerate all admissible partitions with exactly t parts
  # sorted by their greatest part. In step m we add exactly the difference
  # of n and the sum of the entries to get a partition of n.
  #
  # We use the following Lemma: Leaving out the greatest part is a
  # surjective mapping of the set of admissible partitions with k parts
  # to the set of admissible partitions of k-1 parts. Therefore we get
  # every admissible partition with k parts from a partition with k-1
  # parts by adding a part which is greater or equal the greatest part.
  #
  # Note that all our partitions are vectors of length m and until the
  # last step we store n-(the sum) in the first entry.
  #
  local B,BB,i,j,k,p,pos,pp,prototype,t;

  # some special cases:
  if n <= 0 or m < 1 then
    return [];
  elif m = 1 then
    return [[n]];
  fi;
  # from now on we have m >= 2

  prototype := [1..m]*0;

  # Note that there are no admissible partitions of s<n with greatest part
  # greater than QuoInt(n,2) and no one-part-admissible partitions with
  # greatest part greater than QuoInt(n,m):
  # Therefore this is step 1:
  B := [];
  for i in [1..QuoInt(n,m)] do
    B[i] := [ShallowCopy(prototype)];
    B[i][1][1] := n-i;   # remember: here is the sum of the parts
    B[i][1][m] := i;
  od;
  for i in [QuoInt(n,m)+1..QuoInt(n,2)] do
    B[i] := [];
  od;

  # Now to steps 2 to m-1:
  for t in [2..m-1] do
    BB := List([1..QuoInt(n,2)],i->[]);
    pos := m+1-t;  # here we add a number, this is also number of parts to add
    for j in [1..QuoInt(n,2)] do
      # run through B[j] and add greatest part:
      for p in B[j] do
        # add all possible greatest parts:
        for k in [j+1..QuoInt(p[1],pos)] do
          pp := ShallowCopy(p);
          pp[pos] := k;
          pp[1] := pp[1]-k;
          Add(BB[k],pp);
        od;
        p[pos] := j;
        p[1] := p[1]-j;
        Add(BB[j],p);
      od;
    od;
    B := BB;
  od;

  # In step m we only collect everything (the first entry is already OK!):
  BB := List([1..n-m+1],i->[]);
  for j in [1..Length(B)] do
    for p in B[j] do
      Add(BB[p[1]],p);
    od;
  od;
  return Concatenation(BB);
end );


# The following replaces what is now `PartitionsRecursively':
# It now calls `GPartitions' and friends, which is much faster
# and more environment-friendly because it produces less garbage.
# Thanks to Götz Pfeiffer for the ideas!
InstallGlobalFunction(Partitions,function ( n, arg... )
    local   parts, k;
    if not IsInt(n) then
        Error("Partitions: <n> must be an integer");
    fi;
    if Length(arg) = 0  then
        if n <= 0 then
            parts := [[]];
        else
            parts := GPartitions( n );
        fi;
    elif Length(arg) = 1  then
        k := arg[1];
        if not IsInt(k) then
            Error("Partitions: <k> must be an integer");
        fi;
        if n < 0 or k < 0 then
            parts := [];
        else
            if n = 0  then
                if k = 0  then
                    parts := [ [  ] ];
                else
                    parts := [  ];
                fi;
            else
                if k = 0  then
                    parts := [  ];
                else
                    parts := GPartitionsNrParts( n, k );
                fi;
            fi;
        fi;
    else
        ErrorNoReturn("usage: Partitions( <n> [, <k>] )");
    fi;
    return parts;
end);

#############################################################################
##
#F  NrPartitions( <n> [, <k>] ) . . . . .  number of partitions of an integer
##
##  To compute $p(n) = NrPartitions(n)$ we use Euler\'s theorem, that asserts
##  $p(n) = \sum_{k>0}{ (-1)^{k+1} (p(n-(3m^2-m)/2) + p(n-(3m^2+m)/2)) }$.
##
##  To compute $p(n,k)$ we use $p(m,1) = p(m,m) = 1$, $p(m,l) = 0$ if $m\<l$,
##  and the recurrence  $p(m,l) = p(m-1,l-1) + p(m-l,l)$  if $1 \<   l \< m$.
##  This recurrence can be proved by splitting the number of ways to write $m$
##  as a  sum of $l$  summands in two subsets,  those  sums that have  1 as a
##  summand and those that do not.  The number of ways  to write $m$ as a sum
##  of $l$ summands that have 1 as a  summand is $p(m-1,l-1)$, because we can
##  take away  the  1 and obtain  a new sums with   $l-1$ summands  and value
##  $m-1$.  The number of ways to  write  $m$  as a sum  of $l$ summands such
##  that no summand is 1 is $P(m-l,l)$, because we  can  subtract 1 from each
##  summand and obtain new sums that still have $l$ summands but value $m-l$.
##
InstallGlobalFunction(NrPartitions,function ( n, arg... )
    local   s, m, p, k, l;

    if Length(arg) = 0  then
        s := 1;                             # p(0) = 1
        p := [ s ];
        for m  in [1..n]  do
            s := 0;
            k := 1;
            l := 1;                         # k*(3*k-1)/2
            while 0 <= m-(l+k)  do
                s := s - (-1)^k * (p[m-l+1] + p[m-(l+k)+1]);
                k := k + 1;
                l := l + 3*k - 2;
            od;
            if 0 <= m-l  then
                s := s - (-1)^k * p[m-l+1];
            fi;
            p[m+1] := s;
        od;

    elif Length(arg) = 1  then
        k := arg[1];
        if n = k  then
            s := 1;
        elif n < k  or k = 0  then
            s := 0;
        else
            p := [];
            for m  in [1..n]  do
                p[m] := 1;                  # p(m,1) = 1
            od;
            for l  in [2..k]  do
                for m  in [l+1..n-l+1]  do
                    p[m] := p[m] + p[m-l];  # p(m,l) = p(m,l-1) + p(m-l,l)
                od;
            od;
            s := p[n-k+1];
        fi;

    else
        Error("usage: NrPartitions( <n> [, <k>] )");
    fi;

    return s;
end);


#############################################################################
##
#F  PartitionsGreatestLE( <n>, <m> ) . . .  set of partitions of n parts <= m
##
##  returns the set of all (unordered) partitions of the integer <n> having
##  parts less or equal to the integer <m>.
##

BindGlobal( "GPartitionsGreatestLEEasy", function(n,m)
  # Returns a list of all Partitions of n with greatest part less or equal
  # than m, sorted lexicographically.
  # This works essentially as `GPartitions', but the greatest parts are
  # limited.
  # Algorithm/Proof:
  # Let B_n^k be the set of partitions of n with all parts less or equal to k.
  # Then P_n^m := Union_{k=1}^m [k] + B_{n-k}^k}, where "[k]+"
  # means, that a part k is added. Note that the union is a disjoint union.
  # Note that in the end we only need B_{n-k}^k for k<=m but to produce them
  # we need also partial information about B_{n-k}^k for k>m.
  # The algorithm first enumerates B_{n-k}^k for k=1,2,...,m and begins
  # to enumerate B_{n-k}^k for k>m as necessary and then puts everything
  # together by adding the greatest part.
  # The GAP list B has as its j'th entry B[j] := B_{n-j}^j for j=1,...,n-1.
  # Note the greatest part of all partitions in all of B is less than or
  # equal to QuoInt(n,2) and less than or equal to m.
  # The first stage of the algorithm consists of a loop, where k runs
  # from 1 to min(QuoInt(n,2),m) and for each k all partitions are added to all
  # B[j] with greatest part k. Because we run j in descending direction,
  # we already have B[j+k] (partitions of n-j-k) ready up to greatest part k
  # when we handle for B[j] (partitions of n-j) the partitions with greatest
  # part k.
  # In the second stage we only have to add the correct greatest part to get
  # a partition of n.
  # Note that `GPartitionsGreatestLE' improves this by including the
  # work for the second step in the first one, such that less garbage
  # objects are generated.
  # n and m must be a natural numbers >= 1.
  local B,j,k,l,p,res;
  if m >= n then return GPartitions(n); fi;   # a special case
  B := List([1..n-1],x->[]);
  for k in [1..Minimum(QuoInt(n,2),m)] do
    # Now we add all partitions for all entries of B with greatest part k.
    Add(B[n-k],[k]);   # the trivial partition with greatest part k
    for j in [n-k-1,n-k-2..k] do
      # exactly in those are partitions with greatest part k. Think!
      # we handle B[j] (partitions of n-j) with greatest part k
      for p in B[j+k] do    # those are partitions of n-j-k
        l := [k];
        Append(l,p);    # This prolonges the bag without creating garbage!
        Add(B[j],l);
      od;
    od;
  od;
  res := [];    # here we collect the result
  for k in [1..m] do   # handle partitions with greatest part k
    for p in B[k] do     # use B[k] = B_{n-k}^k
      l := [k];          # add a part k
      Append(l,p);
      Add(res,l);        # collect
    od;
  od;
  return res;
end );

BindGlobal( "GPartitionsGreatestLE", function(n,m)
  # Returns a list of all Partitions of n with greatest part less or equal
  # than m, sorted lexicographically.
  # This works exactly as `GPartitionsGreatestLEEasy', but faster.
  # This is done by doing all the work necessary for step 2 already in step 1.
  # n and m must be a natural numbers >= 1.
  local B,j,k,l,p,res;
  if m >= n then return GPartitions(n); fi;   # a special case
  B := List([1..n-1],x->[]);
  for k in [1..Minimum(QuoInt(n,2),m)] do
    # Now we add all partitions for all entries of B with greatest part k.
    Add(B[n-k],[n-k,k]);   # the trivial partition with greatest part k
    for j in [n-k-1,n-k-2..k] do
      # exactly in those are partitions with greatest part k. Think!
      # we handle B[j] (partitions of n-j) with greatest part k
      for p in B[j+k] do    # those are partitions of n-j-k
        l := [j];       # for step 2
        Append(l,p);    # This prolonges the bag without creating garbage!
        l[2] := k;      # here we add a new part k
        Add(B[j],l);
      od;
    od;
  od;
  return Concatenation(B{[1..m]});
end );

InstallGlobalFunction( PartitionsGreatestLE,
function(n,m)
    local parts;
    if not(IsInt(n) and IsInt(m)) then
        ErrorNoReturn("usage: PartitionsGreatestLE( <n>, <m> )");
    elif n < 0 or m < 0 then
        parts := [];
    else
        if n = 0  then
            if m = 0  then
                parts := [ [  ] ];
            else
                parts := [  ];
            fi;
        else
            if m = 0  then
                parts := [  ];
            else
                parts := GPartitionsGreatestLE( n, m );
            fi;
        fi;
    fi;
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.85Quellennavigators  (vorverarbeitet)  ]