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