1*e5dd7070Spatrick"""Utilities for enumeration of finite and countably infinite sets. 2*e5dd7070Spatrick""" 3*e5dd7070Spatrickfrom __future__ import absolute_import, division, print_function 4*e5dd7070Spatrick### 5*e5dd7070Spatrick# Countable iteration 6*e5dd7070Spatrick 7*e5dd7070Spatrick# Simplifies some calculations 8*e5dd7070Spatrickclass Aleph0(int): 9*e5dd7070Spatrick _singleton = None 10*e5dd7070Spatrick def __new__(type): 11*e5dd7070Spatrick if type._singleton is None: 12*e5dd7070Spatrick type._singleton = int.__new__(type) 13*e5dd7070Spatrick return type._singleton 14*e5dd7070Spatrick def __repr__(self): return '<aleph0>' 15*e5dd7070Spatrick def __str__(self): return 'inf' 16*e5dd7070Spatrick 17*e5dd7070Spatrick def __cmp__(self, b): 18*e5dd7070Spatrick return 1 19*e5dd7070Spatrick 20*e5dd7070Spatrick def __sub__(self, b): 21*e5dd7070Spatrick raise ValueError("Cannot subtract aleph0") 22*e5dd7070Spatrick __rsub__ = __sub__ 23*e5dd7070Spatrick 24*e5dd7070Spatrick def __add__(self, b): 25*e5dd7070Spatrick return self 26*e5dd7070Spatrick __radd__ = __add__ 27*e5dd7070Spatrick 28*e5dd7070Spatrick def __mul__(self, b): 29*e5dd7070Spatrick if b == 0: return b 30*e5dd7070Spatrick return self 31*e5dd7070Spatrick __rmul__ = __mul__ 32*e5dd7070Spatrick 33*e5dd7070Spatrick def __floordiv__(self, b): 34*e5dd7070Spatrick if b == 0: raise ZeroDivisionError 35*e5dd7070Spatrick return self 36*e5dd7070Spatrick __rfloordiv__ = __floordiv__ 37*e5dd7070Spatrick __truediv__ = __floordiv__ 38*e5dd7070Spatrick __rtuediv__ = __floordiv__ 39*e5dd7070Spatrick __div__ = __floordiv__ 40*e5dd7070Spatrick __rdiv__ = __floordiv__ 41*e5dd7070Spatrick 42*e5dd7070Spatrick def __pow__(self, b): 43*e5dd7070Spatrick if b == 0: return 1 44*e5dd7070Spatrick return self 45*e5dd7070Spatrickaleph0 = Aleph0() 46*e5dd7070Spatrick 47*e5dd7070Spatrickdef base(line): 48*e5dd7070Spatrick return line*(line+1)//2 49*e5dd7070Spatrick 50*e5dd7070Spatrickdef pairToN(pair): 51*e5dd7070Spatrick x,y = pair 52*e5dd7070Spatrick line,index = x+y,y 53*e5dd7070Spatrick return base(line)+index 54*e5dd7070Spatrick 55*e5dd7070Spatrickdef getNthPairInfo(N): 56*e5dd7070Spatrick # Avoid various singularities 57*e5dd7070Spatrick if N==0: 58*e5dd7070Spatrick return (0,0) 59*e5dd7070Spatrick 60*e5dd7070Spatrick # Gallop to find bounds for line 61*e5dd7070Spatrick line = 1 62*e5dd7070Spatrick next = 2 63*e5dd7070Spatrick while base(next)<=N: 64*e5dd7070Spatrick line = next 65*e5dd7070Spatrick next = line << 1 66*e5dd7070Spatrick 67*e5dd7070Spatrick # Binary search for starting line 68*e5dd7070Spatrick lo = line 69*e5dd7070Spatrick hi = line<<1 70*e5dd7070Spatrick while lo + 1 != hi: 71*e5dd7070Spatrick #assert base(lo) <= N < base(hi) 72*e5dd7070Spatrick mid = (lo + hi)>>1 73*e5dd7070Spatrick if base(mid)<=N: 74*e5dd7070Spatrick lo = mid 75*e5dd7070Spatrick else: 76*e5dd7070Spatrick hi = mid 77*e5dd7070Spatrick 78*e5dd7070Spatrick line = lo 79*e5dd7070Spatrick return line, N - base(line) 80*e5dd7070Spatrick 81*e5dd7070Spatrickdef getNthPair(N): 82*e5dd7070Spatrick line,index = getNthPairInfo(N) 83*e5dd7070Spatrick return (line - index, index) 84*e5dd7070Spatrick 85*e5dd7070Spatrickdef getNthPairBounded(N,W=aleph0,H=aleph0,useDivmod=False): 86*e5dd7070Spatrick """getNthPairBounded(N, W, H) -> (x, y) 87*e5dd7070Spatrick 88*e5dd7070Spatrick Return the N-th pair such that 0 <= x < W and 0 <= y < H.""" 89*e5dd7070Spatrick 90*e5dd7070Spatrick if W <= 0 or H <= 0: 91*e5dd7070Spatrick raise ValueError("Invalid bounds") 92*e5dd7070Spatrick elif N >= W*H: 93*e5dd7070Spatrick raise ValueError("Invalid input (out of bounds)") 94*e5dd7070Spatrick 95*e5dd7070Spatrick # Simple case... 96*e5dd7070Spatrick if W is aleph0 and H is aleph0: 97*e5dd7070Spatrick return getNthPair(N) 98*e5dd7070Spatrick 99*e5dd7070Spatrick # Otherwise simplify by assuming W < H 100*e5dd7070Spatrick if H < W: 101*e5dd7070Spatrick x,y = getNthPairBounded(N,H,W,useDivmod=useDivmod) 102*e5dd7070Spatrick return y,x 103*e5dd7070Spatrick 104*e5dd7070Spatrick if useDivmod: 105*e5dd7070Spatrick return N%W,N//W 106*e5dd7070Spatrick else: 107*e5dd7070Spatrick # Conceptually we want to slide a diagonal line across a 108*e5dd7070Spatrick # rectangle. This gives more interesting results for large 109*e5dd7070Spatrick # bounds than using divmod. 110*e5dd7070Spatrick 111*e5dd7070Spatrick # If in lower left, just return as usual 112*e5dd7070Spatrick cornerSize = base(W) 113*e5dd7070Spatrick if N < cornerSize: 114*e5dd7070Spatrick return getNthPair(N) 115*e5dd7070Spatrick 116*e5dd7070Spatrick # Otherwise if in upper right, subtract from corner 117*e5dd7070Spatrick if H is not aleph0: 118*e5dd7070Spatrick M = W*H - N - 1 119*e5dd7070Spatrick if M < cornerSize: 120*e5dd7070Spatrick x,y = getNthPair(M) 121*e5dd7070Spatrick return (W-1-x,H-1-y) 122*e5dd7070Spatrick 123*e5dd7070Spatrick # Otherwise, compile line and index from number of times we 124*e5dd7070Spatrick # wrap. 125*e5dd7070Spatrick N = N - cornerSize 126*e5dd7070Spatrick index,offset = N%W,N//W 127*e5dd7070Spatrick # p = (W-1, 1+offset) + (-1,1)*index 128*e5dd7070Spatrick return (W-1-index, 1+offset+index) 129*e5dd7070Spatrickdef getNthPairBoundedChecked(N,W=aleph0,H=aleph0,useDivmod=False,GNP=getNthPairBounded): 130*e5dd7070Spatrick x,y = GNP(N,W,H,useDivmod) 131*e5dd7070Spatrick assert 0 <= x < W and 0 <= y < H 132*e5dd7070Spatrick return x,y 133*e5dd7070Spatrick 134*e5dd7070Spatrickdef getNthNTuple(N, W, H=aleph0, useLeftToRight=False): 135*e5dd7070Spatrick """getNthNTuple(N, W, H) -> (x_0, x_1, ..., x_W) 136*e5dd7070Spatrick 137*e5dd7070Spatrick Return the N-th W-tuple, where for 0 <= x_i < H.""" 138*e5dd7070Spatrick 139*e5dd7070Spatrick if useLeftToRight: 140*e5dd7070Spatrick elts = [None]*W 141*e5dd7070Spatrick for i in range(W): 142*e5dd7070Spatrick elts[i],N = getNthPairBounded(N, H) 143*e5dd7070Spatrick return tuple(elts) 144*e5dd7070Spatrick else: 145*e5dd7070Spatrick if W==0: 146*e5dd7070Spatrick return () 147*e5dd7070Spatrick elif W==1: 148*e5dd7070Spatrick return (N,) 149*e5dd7070Spatrick elif W==2: 150*e5dd7070Spatrick return getNthPairBounded(N, H, H) 151*e5dd7070Spatrick else: 152*e5dd7070Spatrick LW,RW = W//2, W - (W//2) 153*e5dd7070Spatrick L,R = getNthPairBounded(N, H**LW, H**RW) 154*e5dd7070Spatrick return (getNthNTuple(L,LW,H=H,useLeftToRight=useLeftToRight) + 155*e5dd7070Spatrick getNthNTuple(R,RW,H=H,useLeftToRight=useLeftToRight)) 156*e5dd7070Spatrickdef getNthNTupleChecked(N, W, H=aleph0, useLeftToRight=False, GNT=getNthNTuple): 157*e5dd7070Spatrick t = GNT(N,W,H,useLeftToRight) 158*e5dd7070Spatrick assert len(t) == W 159*e5dd7070Spatrick for i in t: 160*e5dd7070Spatrick assert i < H 161*e5dd7070Spatrick return t 162*e5dd7070Spatrick 163*e5dd7070Spatrickdef getNthTuple(N, maxSize=aleph0, maxElement=aleph0, useDivmod=False, useLeftToRight=False): 164*e5dd7070Spatrick """getNthTuple(N, maxSize, maxElement) -> x 165*e5dd7070Spatrick 166*e5dd7070Spatrick Return the N-th tuple where len(x) < maxSize and for y in x, 0 <= 167*e5dd7070Spatrick y < maxElement.""" 168*e5dd7070Spatrick 169*e5dd7070Spatrick # All zero sized tuples are isomorphic, don't ya know. 170*e5dd7070Spatrick if N == 0: 171*e5dd7070Spatrick return () 172*e5dd7070Spatrick N -= 1 173*e5dd7070Spatrick if maxElement is not aleph0: 174*e5dd7070Spatrick if maxSize is aleph0: 175*e5dd7070Spatrick raise NotImplementedError('Max element size without max size unhandled') 176*e5dd7070Spatrick bounds = [maxElement**i for i in range(1, maxSize+1)] 177*e5dd7070Spatrick S,M = getNthPairVariableBounds(N, bounds) 178*e5dd7070Spatrick else: 179*e5dd7070Spatrick S,M = getNthPairBounded(N, maxSize, useDivmod=useDivmod) 180*e5dd7070Spatrick return getNthNTuple(M, S+1, maxElement, useLeftToRight=useLeftToRight) 181*e5dd7070Spatrickdef getNthTupleChecked(N, maxSize=aleph0, maxElement=aleph0, 182*e5dd7070Spatrick useDivmod=False, useLeftToRight=False, GNT=getNthTuple): 183*e5dd7070Spatrick # FIXME: maxsize is inclusive 184*e5dd7070Spatrick t = GNT(N,maxSize,maxElement,useDivmod,useLeftToRight) 185*e5dd7070Spatrick assert len(t) <= maxSize 186*e5dd7070Spatrick for i in t: 187*e5dd7070Spatrick assert i < maxElement 188*e5dd7070Spatrick return t 189*e5dd7070Spatrick 190*e5dd7070Spatrickdef getNthPairVariableBounds(N, bounds): 191*e5dd7070Spatrick """getNthPairVariableBounds(N, bounds) -> (x, y) 192*e5dd7070Spatrick 193*e5dd7070Spatrick Given a finite list of bounds (which may be finite or aleph0), 194*e5dd7070Spatrick return the N-th pair such that 0 <= x < len(bounds) and 0 <= y < 195*e5dd7070Spatrick bounds[x].""" 196*e5dd7070Spatrick 197*e5dd7070Spatrick if not bounds: 198*e5dd7070Spatrick raise ValueError("Invalid bounds") 199*e5dd7070Spatrick if not (0 <= N < sum(bounds)): 200*e5dd7070Spatrick raise ValueError("Invalid input (out of bounds)") 201*e5dd7070Spatrick 202*e5dd7070Spatrick level = 0 203*e5dd7070Spatrick active = list(range(len(bounds))) 204*e5dd7070Spatrick active.sort(key=lambda i: bounds[i]) 205*e5dd7070Spatrick prevLevel = 0 206*e5dd7070Spatrick for i,index in enumerate(active): 207*e5dd7070Spatrick level = bounds[index] 208*e5dd7070Spatrick W = len(active) - i 209*e5dd7070Spatrick if level is aleph0: 210*e5dd7070Spatrick H = aleph0 211*e5dd7070Spatrick else: 212*e5dd7070Spatrick H = level - prevLevel 213*e5dd7070Spatrick levelSize = W*H 214*e5dd7070Spatrick if N<levelSize: # Found the level 215*e5dd7070Spatrick idelta,delta = getNthPairBounded(N, W, H) 216*e5dd7070Spatrick return active[i+idelta],prevLevel+delta 217*e5dd7070Spatrick else: 218*e5dd7070Spatrick N -= levelSize 219*e5dd7070Spatrick prevLevel = level 220*e5dd7070Spatrick else: 221*e5dd7070Spatrick raise RuntimError("Unexpected loop completion") 222*e5dd7070Spatrick 223*e5dd7070Spatrickdef getNthPairVariableBoundsChecked(N, bounds, GNVP=getNthPairVariableBounds): 224*e5dd7070Spatrick x,y = GNVP(N,bounds) 225*e5dd7070Spatrick assert 0 <= x < len(bounds) and 0 <= y < bounds[x] 226*e5dd7070Spatrick return (x,y) 227*e5dd7070Spatrick 228*e5dd7070Spatrick### 229*e5dd7070Spatrick 230*e5dd7070Spatrickdef testPairs(): 231*e5dd7070Spatrick W = 3 232*e5dd7070Spatrick H = 6 233*e5dd7070Spatrick a = [[' ' for x in range(10)] for y in range(10)] 234*e5dd7070Spatrick b = [[' ' for x in range(10)] for y in range(10)] 235*e5dd7070Spatrick for i in range(min(W*H,40)): 236*e5dd7070Spatrick x,y = getNthPairBounded(i,W,H) 237*e5dd7070Spatrick x2,y2 = getNthPairBounded(i,W,H,useDivmod=True) 238*e5dd7070Spatrick print(i,(x,y),(x2,y2)) 239*e5dd7070Spatrick a[y][x] = '%2d'%i 240*e5dd7070Spatrick b[y2][x2] = '%2d'%i 241*e5dd7070Spatrick 242*e5dd7070Spatrick print('-- a --') 243*e5dd7070Spatrick for ln in a[::-1]: 244*e5dd7070Spatrick if ''.join(ln).strip(): 245*e5dd7070Spatrick print(' '.join(ln)) 246*e5dd7070Spatrick print('-- b --') 247*e5dd7070Spatrick for ln in b[::-1]: 248*e5dd7070Spatrick if ''.join(ln).strip(): 249*e5dd7070Spatrick print(' '.join(ln)) 250*e5dd7070Spatrick 251*e5dd7070Spatrickdef testPairsVB(): 252*e5dd7070Spatrick bounds = [2,2,4,aleph0,5,aleph0] 253*e5dd7070Spatrick a = [[' ' for x in range(15)] for y in range(15)] 254*e5dd7070Spatrick b = [[' ' for x in range(15)] for y in range(15)] 255*e5dd7070Spatrick for i in range(min(sum(bounds),40)): 256*e5dd7070Spatrick x,y = getNthPairVariableBounds(i, bounds) 257*e5dd7070Spatrick print(i,(x,y)) 258*e5dd7070Spatrick a[y][x] = '%2d'%i 259*e5dd7070Spatrick 260*e5dd7070Spatrick print('-- a --') 261*e5dd7070Spatrick for ln in a[::-1]: 262*e5dd7070Spatrick if ''.join(ln).strip(): 263*e5dd7070Spatrick print(' '.join(ln)) 264*e5dd7070Spatrick 265*e5dd7070Spatrick### 266*e5dd7070Spatrick 267*e5dd7070Spatrick# Toggle to use checked versions of enumeration routines. 268*e5dd7070Spatrickif False: 269*e5dd7070Spatrick getNthPairVariableBounds = getNthPairVariableBoundsChecked 270*e5dd7070Spatrick getNthPairBounded = getNthPairBoundedChecked 271*e5dd7070Spatrick getNthNTuple = getNthNTupleChecked 272*e5dd7070Spatrick getNthTuple = getNthTupleChecked 273*e5dd7070Spatrick 274*e5dd7070Spatrickif __name__ == '__main__': 275*e5dd7070Spatrick testPairs() 276*e5dd7070Spatrick 277*e5dd7070Spatrick testPairsVB() 278*e5dd7070Spatrick 279