You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
7.7 KiB

"""Utilities for enumeration of finite and countably infinite sets.
"""
from __future__ import absolute_import, division, print_function
###
# Countable iteration
# Simplifies some calculations
class Aleph0(int):
_singleton = None
def __new__(type):
if type._singleton is None:
type._singleton = int.__new__(type)
return type._singleton
def __repr__(self): return '<aleph0>'
def __str__(self): return 'inf'
def __cmp__(self, b):
return 1
def __sub__(self, b):
raise ValueError("Cannot subtract aleph0")
__rsub__ = __sub__
def __add__(self, b):
return self
__radd__ = __add__
def __mul__(self, b):
if b == 0: return b
return self
__rmul__ = __mul__
def __floordiv__(self, b):
if b == 0: raise ZeroDivisionError
return self
__rfloordiv__ = __floordiv__
__truediv__ = __floordiv__
__rtuediv__ = __floordiv__
__div__ = __floordiv__
__rdiv__ = __floordiv__
def __pow__(self, b):
if b == 0: return 1
return self
aleph0 = Aleph0()
def base(line):
return line*(line+1)//2
def pairToN(pair):
x,y = pair
line,index = x+y,y
return base(line)+index
def getNthPairInfo(N):
# Avoid various singularities
if N==0:
return (0,0)
# Gallop to find bounds for line
line = 1
next = 2
while base(next)<=N:
line = next
next = line << 1
# Binary search for starting line
lo = line
hi = line<<1
while lo + 1 != hi:
#assert base(lo) <= N < base(hi)
mid = (lo + hi)>>1
if base(mid)<=N:
lo = mid
else:
hi = mid
line = lo
return line, N - base(line)
def getNthPair(N):
line,index = getNthPairInfo(N)
return (line - index, index)
def getNthPairBounded(N,W=aleph0,H=aleph0,useDivmod=False):
"""getNthPairBounded(N, W, H) -> (x, y)
Return the N-th pair such that 0 <= x < W and 0 <= y < H."""
if W <= 0 or H <= 0:
raise ValueError("Invalid bounds")
elif N >= W*H:
raise ValueError("Invalid input (out of bounds)")
# Simple case...
if W is aleph0 and H is aleph0:
return getNthPair(N)
# Otherwise simplify by assuming W < H
if H < W:
x,y = getNthPairBounded(N,H,W,useDivmod=useDivmod)
return y,x
if useDivmod:
return N%W,N//W
else:
# Conceptually we want to slide a diagonal line across a
# rectangle. This gives more interesting results for large
# bounds than using divmod.
# If in lower left, just return as usual
cornerSize = base(W)
if N < cornerSize:
return getNthPair(N)
# Otherwise if in upper right, subtract from corner
if H is not aleph0:
M = W*H - N - 1
if M < cornerSize:
x,y = getNthPair(M)
return (W-1-x,H-1-y)
# Otherwise, compile line and index from number of times we
# wrap.
N = N - cornerSize
index,offset = N%W,N//W
# p = (W-1, 1+offset) + (-1,1)*index
return (W-1-index, 1+offset+index)
def getNthPairBoundedChecked(N,W=aleph0,H=aleph0,useDivmod=False,GNP=getNthPairBounded):
x,y = GNP(N,W,H,useDivmod)
assert 0 <= x < W and 0 <= y < H
return x,y
def getNthNTuple(N, W, H=aleph0, useLeftToRight=False):
"""getNthNTuple(N, W, H) -> (x_0, x_1, ..., x_W)
Return the N-th W-tuple, where for 0 <= x_i < H."""
if useLeftToRight:
elts = [None]*W
for i in range(W):
elts[i],N = getNthPairBounded(N, H)
return tuple(elts)
else:
if W==0:
return ()
elif W==1:
return (N,)
elif W==2:
return getNthPairBounded(N, H, H)
else:
LW,RW = W//2, W - (W//2)
L,R = getNthPairBounded(N, H**LW, H**RW)
return (getNthNTuple(L,LW,H=H,useLeftToRight=useLeftToRight) +
getNthNTuple(R,RW,H=H,useLeftToRight=useLeftToRight))
def getNthNTupleChecked(N, W, H=aleph0, useLeftToRight=False, GNT=getNthNTuple):
t = GNT(N,W,H,useLeftToRight)
assert len(t) == W
for i in t:
assert i < H
return t
def getNthTuple(N, maxSize=aleph0, maxElement=aleph0, useDivmod=False, useLeftToRight=False):
"""getNthTuple(N, maxSize, maxElement) -> x
Return the N-th tuple where len(x) < maxSize and for y in x, 0 <=
y < maxElement."""
# All zero sized tuples are isomorphic, don't ya know.
if N == 0:
return ()
N -= 1
if maxElement is not aleph0:
if maxSize is aleph0:
raise NotImplementedError('Max element size without max size unhandled')
bounds = [maxElement**i for i in range(1, maxSize+1)]
S,M = getNthPairVariableBounds(N, bounds)
else:
S,M = getNthPairBounded(N, maxSize, useDivmod=useDivmod)
return getNthNTuple(M, S+1, maxElement, useLeftToRight=useLeftToRight)
def getNthTupleChecked(N, maxSize=aleph0, maxElement=aleph0,
useDivmod=False, useLeftToRight=False, GNT=getNthTuple):
# FIXME: maxsize is inclusive
t = GNT(N,maxSize,maxElement,useDivmod,useLeftToRight)
assert len(t) <= maxSize
for i in t:
assert i < maxElement
return t
def getNthPairVariableBounds(N, bounds):
"""getNthPairVariableBounds(N, bounds) -> (x, y)
Given a finite list of bounds (which may be finite or aleph0),
return the N-th pair such that 0 <= x < len(bounds) and 0 <= y <
bounds[x]."""
if not bounds:
raise ValueError("Invalid bounds")
if not (0 <= N < sum(bounds)):
raise ValueError("Invalid input (out of bounds)")
level = 0
active = list(range(len(bounds)))
active.sort(key=lambda i: bounds[i])
prevLevel = 0
for i,index in enumerate(active):
level = bounds[index]
W = len(active) - i
if level is aleph0:
H = aleph0
else:
H = level - prevLevel
levelSize = W*H
if N<levelSize: # Found the level
idelta,delta = getNthPairBounded(N, W, H)
return active[i+idelta],prevLevel+delta
else:
N -= levelSize
prevLevel = level
else:
raise RuntimError("Unexpected loop completion")
def getNthPairVariableBoundsChecked(N, bounds, GNVP=getNthPairVariableBounds):
x,y = GNVP(N,bounds)
assert 0 <= x < len(bounds) and 0 <= y < bounds[x]
return (x,y)
###
def testPairs():
W = 3
H = 6
a = [[' ' for x in range(10)] for y in range(10)]
b = [[' ' for x in range(10)] for y in range(10)]
for i in range(min(W*H,40)):
x,y = getNthPairBounded(i,W,H)
x2,y2 = getNthPairBounded(i,W,H,useDivmod=True)
print(i,(x,y),(x2,y2))
a[y][x] = '%2d'%i
b[y2][x2] = '%2d'%i
print('-- a --')
for ln in a[::-1]:
if ''.join(ln).strip():
print(' '.join(ln))
print('-- b --')
for ln in b[::-1]:
if ''.join(ln).strip():
print(' '.join(ln))
def testPairsVB():
bounds = [2,2,4,aleph0,5,aleph0]
a = [[' ' for x in range(15)] for y in range(15)]
b = [[' ' for x in range(15)] for y in range(15)]
for i in range(min(sum(bounds),40)):
x,y = getNthPairVariableBounds(i, bounds)
print(i,(x,y))
a[y][x] = '%2d'%i
print('-- a --')
for ln in a[::-1]:
if ''.join(ln).strip():
print(' '.join(ln))
###
# Toggle to use checked versions of enumeration routines.
if False:
getNthPairVariableBounds = getNthPairVariableBoundsChecked
getNthPairBounded = getNthPairBoundedChecked
getNthNTuple = getNthNTupleChecked
getNthTuple = getNthTupleChecked
if __name__ == '__main__':
testPairs()
testPairsVB()