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
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()
|
|
|