[Matrix-SIG] ANNOUNCE: Gnuplot.py 1.3
Tim Peters
tim_one@email.msn.com
Sat, 25 Sep 1999 03:17:41 -0400
[Czerminski, Ryszard]
> I am looking for a function which could compute
> combinations given lexicographical index e.g.
>
> C(1;3,2) -> 1,2
> C(2;3,2) -> 1,3
> C(3;3,2) -> 2,3
>
> I have found function which does this
> ( http://math.nist.gov/GAMS.html ) but it is limited
> to small numbers since it is using regular 4 bytes
> representation for integers and therefore index
> range is severly limited ( < 2^32 ).
As is often the case, relaxing such limits creates other problems; in this
case, the Fortran TOMS 515 algorithm recomputes the number of combinations
from scratch each time thru the inner loop, which is very expensive if
choosing many elements from a very large set.
> Any pointers to the software which does this for
> integers of arbitrary length would be very much
> appreciated.
One is attached. Note that since this is Python instead of Fortran,
everything is 0-based. That is, when asking for the i'th combination of m
things taken n at a time, i ranges from 0 up to but not including C(m, n),
and the "canonical set" is taken to be range(m). The inner loop avoids the
problem above, so this is reasonably fast even for multi-hundred digit
indices.
more-combinations-than-a-reasonable-person-could-want<wink>-ly y'rs - tim
def _chop(n):
"""n -> int if it fits, else long."""
try:
return int(n)
except OverflowError:
return n
def comb(m, n):
"""m, n -> number of combinations of m items, n at a time.
m >= n >= 0 required.
"""
if not m >= n >= 0:
raise ValueError("m >= n >= 0 required: " + `m, n`)
if n > (m >> 1):
n = m-n
if n == 0:
return 1
result = long(m)
i = 2
m, n = m-1, n-1
while n:
# assert (result * m) % i == 0
result = result * m / i
i = i+1
n = n-1
m = m-1
return _chop(result)
def combatindex(m, n, i):
"""m, n, i -> i'th combination of m items taken n at a time.
m >= n >= 1 and 0 <= i < comb(m, n) required.
Return the i'th combination in lexicographic order, as a list
of n elements taken from range(m).
The index (i) is 0-based.
Example:
>>> for i in range(6):
... print combatindex(4, 2, i)
[0, 1]
[0, 2]
[0, 3]
[1, 2]
[1, 3]
[2, 3]
"""
if not m >= n >= 1:
raise ValueError("m >= n >= 1 required: " + `m, n`)
c = long(comb(m, n))
if not 0 <= i < c:
raise ValueError("0 <= i < comb(m,n) required: " + `i, c`)
result = []
# have c == comb(m, n), want comb(m-1,n-1)
c = c * n / m
# invariant: c == comb(m-1, n-1)
for element in xrange(m):
if i < c:
# take this element, and n-1 from the remaining
result.append(element)
n = n-1
if n == 0:
break
# have c == comb(m-1,n), want comb(m-2,n-1)
c = c * n / (m-1)
else:
# skip this element, and take all from the remaining
i = i-c
# have c == comb(m-1,n-1), want comb(m-2,n-1)
c = c * (m-n) / (m-1)
m = m-1
assert i == 0
return result
def _test():
failures = 0
m, n = 10, 6
c = comb(m, n)
last = [0] * n
for i in xrange(c):
this = combatindex(m, n, i)
if len(this) != n:
print "*** length error:", m, n, i, this
failures = failures + 1
if not last < this:
print "*** lexicographic error:", m, n, i, last, this
failures = failures + 1
last = this
if this != range(m-n, m):
print "*** last value wrong:", m, n, c-1, this
failures = failures + 1
# c has more than 1400 digits in the next case -- may take a
# few seconds
m, n = 5000, 2000
c = comb(m, n)
this = combatindex(m, n, 0)
if this != range(n):
print "*** first value wrong:", m, n, 0, this
failures = failures + 1
this = combatindex(m, n, c-1)
if this != range(m-n, m):
print "*** last value wrong:", m, n, c-1, this
failures = failures + 1
if failures:
print "*********** TEST FAILED *************"
if __name__ == "__main__":
_test()