comparison nono.py @ 0:fee51ab07d09

blanket publication of all existing python files in lib/python on maritain
author Henry S. Thompson <ht@inf.ed.ac.uk>
date Mon, 09 Mar 2020 14:58:04 +0000
parents
children a56d5285575b
comparison
equal deleted inserted replaced
-1:000000000000 0:fee51ab07d09
1 #!/usr/bin/python3
2 # Expects e.g. ^A copy from Nonograms dprint preview cols, then blank line, then rows
3 # rows are space-separated
4 # cols are one-digit-after-another, unless some 2-digit, in which case x is separator
5 # E.g.
6 # 13x1x2
7 # 19
8 # maps to
9 # 13
10 # 1 1
11 # 2 9
12
13 import sys
14
15 Red=''
16 eRed=''
17 RedFmt=Red+'%s'+eRed
18
19 def interleave(*args):
20 for vals in zip(*args):
21 yield from vals
22
23 class Vector(list):
24 # reads top-to-bottom or left-to-right
25 def __init__(self,n,m,runs):
26 list.__init__(self,list(range(n)))
27 self.n=n
28 self.runs=runs
29 # compute the set of all possible layouts for runs
30 self.rn=len(self.runs)
31 rtot=sum(self.runs)
32 self.allRuns=list(self.seedList(0,0,0,
33 sum(1+self.runs[k] for k in range(self.rn))-1))
34 self.nar=len(self.allRuns)
35
36 def seedList(self,i,j,pos,runLen):
37 """
38 :param i: starting skip before next run
39 :type i: 0 if pos==0 else 1
40 :param j: next run number
41 :type j: int
42 :param pos: left margin
43 :type pos: int
44 """
45 bound=self.n-(pos+runLen)+1
46 #dprint('s',i,j,pos,runLen,bound)
47 if j==self.rn:
48 yield []
49 return
50 r=self.runs[j]
51 for v in range(i,bound):
52 for sub in self.seedList(1,j+1,pos+v+r,runLen-(r+1)):
53 yield [-v,r]+sub
54
55 def __repr__(self):
56 return "V@%s%s:%s"%(self.x,self.runs,list.__repr__(self))
57
58 def __str__(self):
59 return '%s|'%('|'.join(str(c) for c in self))
60
61 def step(self):
62 scratch=[0 if c.val is None else c.val for c in self]
63 for k,runs in enumerate(self.allRuns):
64 dprint('=====pass %s======'%k)
65 self.onepass(0,self.n,scratch,runs.copy())
66 dprint(scratch)
67 for i in range(self.n):
68 if scratch[i]==self.nar:
69 # If blobby in _every_ pass, then must be a blob
70 if self[i].val is None:
71 self[i].setVal(True)
72 elif self[i].val is True:
73 # already there
74 pass
75 else:
76 print("Shouldn't happen: attempt to blob where x already present! %s at %s"%(self,i),file=sys.stderr)
77 exit(101)
78
79 def onepass(self,i0,iBound,scratch,stack):
80 """note that stack is not a simple run, but one with _negative_ numbers between
81 and possibly before the positive ones, indicating obligatory skips
82 """
83 i=i0 # starting index into self/scratch/maybe
84 j=-1 # index into run
85 maybe=[0]*iBound
86 dprint('r: %s'%stack)
87 req=sum((-r if r<0 else r) for r in stack)
88 while stack and i<iBound:
89 r=rr=stack.pop(0)
90 dprint('pop:',r)
91 if r<1:
92 # obligatory skip
93 # (Above init of self.allRuns is easier if we allow a 0 to be ignored
94 i-=r
95 req+=r
96 r=rr=stack.pop(0)
97 # rr is run remaining -- how many we still need
98 j+=1 # index of current run in self.runs, we'll need to decorate that eventually
99 inOne=-1 # if non-neg, records the start point of a possible run
100 gapsFilled=0
101 # First, check if we can start here: 0 is OK, and n>0 iff n-1 is None or False
102 if i>0 and i<iBound:
103 while self[i-1].val:
104 i+=1
105 if (iBound-i)<req:
106 # Can't win, give up altogether
107 dprint('c0',i,iBound,req)
108 return
109 while i<iBound:
110 c=self[i].val
111 dprint('top',i,c,inOne,rr)
112 if c is None:
113 # we could add a blob here
114 dprint('c1')
115 gapsFilled+=1
116 rr-=1
117 if inOne<0:
118 dprint('c1a',i)
119 # starts here
120 inOne=i
121 # fall through to check for completion
122 else:
123 dprint('c2')
124 # c is a bool
125 if inOne<0:
126 dprint('c2a')
127 if c:
128 dprint('c2a1')
129 # a *, we can possible start something here
130 inOne=i
131 rr-=1
132 # fall through to check for completion
133 else:
134 dprint('c2a2')
135 # an x, can't start here, just move along
136 i+=1
137 continue
138 else:
139 dprint('c2b')
140 if c:
141 dprint('c2b1')
142 # a blob, extend or complete a partial
143 rr-=1
144 # fall through to check for completion
145 else:
146 # abandon a partial
147 dprint('c2b2')
148 inOne=-1
149 rr=r
150 i+=1
151 continue
152 if rr>0:
153 dprint('c3')
154 # we're not done, carry on
155 i+=1
156 continue
157 # Maybe a win?
158 # look ahead, can we stop here?
159 # NB _self_.n
160 if i+1<self.n and self[i+1].val:
161 dprint('c4')
162 # Nope
163 inOne=-1
164 rr=r
165 gapsFilled=0
166 i+=1
167 continue
168 elif gapsFilled==0:
169 dprint('c5')
170 # We must have crossed at least on gap...
171 print("Shouldn't happen: no gap! me:%s i:%s j:%s rr:%s inOne:%s"%(self,i, j, rr, inOne),file=sys.stderr)
172 exit(100)
173 # Victory!
174 dprint('c6',r,inOne,i)
175 for k in range(inOne,i+1):
176 maybe[k]+=1
177 i+=1
178 req-=r
179 break
180 # on to the next run
181 # end of inner loop, did we win?
182 if (not stack) or i==iBound:
183 # yes
184 dprint('win:',maybe)
185 for k in range(iBound):
186 scratch[k]+=maybe[k]
187
188 class Row(Vector):
189 def __init__(self,n,m,runs,pos,dprintWidth):
190 Vector.__init__(self,n,m,runs)
191 self.y=pos
192 self.dprintWidth=dprintWidth
193 self.fmt="%%%ss|"%dprintWidth
194
195 def __str__(self):
196 return ((self.fmt%(' '.join(str(r) for r in self.runs)))+
197 Vector.__str__(self))
198
199 class Column(Vector):
200 def __init__(self,n,m,runs,pos,dprintHeight):
201 Vector.__init__(self,n,m,runs)
202 self.x=pos
203 self.dprintHeight=dprintHeight
204 self.fmt="%%%ss"%self.dprintHeight
205 self.updateHeader()
206
207 def updateHeader(self):
208 header=('-'.join(str(c) for c in self.runs))
209 self.header=self.fmt%header # pad to same 'height'
210
211 class Cell:
212 def __init__(self,row,y,column,x):
213 # At the intersection of row and column Vectors
214 self.row=row
215 self.column=column
216 self.x=x
217 self.y=y
218 self.val=None # three valued: None(unknown), True(filled), False(empty)
219 self.row[x]=self
220 self.column[y]=self
221
222 def __repr__(self):
223 return "C@(%s,%s):%s"%(self.x,self.y,self.val)
224
225 def __str__(self):
226 return ' ' if self.val is None else ('\u25A0' if self.val else 'x')
227
228 def setVal(self,v):
229 if v is True:
230 if self.val is False:
231 dprint("Warning: x -> * at %s,%s"%(self.x,self.y))
232 elif self.val is True:
233 # No-op
234 return
235 # @@ check row/col completed
236 else:
237 if self.val is not None:
238 dprint("Warning: %s -> %s at %s,%s"%(self.val,v,self.x,self.y))
239 self.val=v
240
241 class Nono(dict):
242 # 0,0 is upper left, so increasing y goes _downwards_, to match the standard layout
243 def __init__(self,rows,cols):
244 n=self.n=len(cols)
245 if n!=len(rows):
246 print("losing r:%s x c:%s"%(len(rows),n),sys.stderr)
247 exit(1)
248 self.rc=rows
249 rowDprintWidth=max(sum(len(str(r)) for r in row)+len(row)-1 for row in rows)
250 self.rowfmt="%s|%%s"%(' '*rowDprintWidth)
251 self.cc=cols
252 # dprint col nums>9 vertically :-(
253 self.colDprintHeight=max(sum(len(str(c)) for c in col)+len(col)-1 for col in cols)
254 self.columns=cc=[Column(n,self,cols[i],i,self.colDprintHeight) for i in range(20)]
255 self.rows=rr=[Row(n,self,rows[i],i,rowDprintWidth) for i in range(20)]
256 for x in range(20):
257 for y in range(20):
258 self[(x,y)]=Cell(rr[y],y,cc[x],x)
259
260 def __str__(self):
261 lines=[self.rowfmt%('|'.join([(self.columns[i]).header[j] for i in range(self.n)])) # 'rotate'
262 for j in range(self.colDprintHeight)]
263 lines+=[str(r) for r in self.rows]
264 return "\n".join(lines)
265
266 def dprint(*args):
267 pass
268
269 if __name__ == '__main__':
270 if len(sys.argv)>1:
271 f=open(sys.argv[1])
272 else:
273 f=sys.stdin
274
275 cols=[]
276
277 for l in f:
278 l=l.rstrip()
279 if l=='':
280 break
281 if 'x' in l:
282 vv=[int(s) for s in l.split('x')]
283 else:
284 vv=[int(c) for c in l]
285 cols.append(vv)
286
287 rows=[[int(s) for s in l.split()] for l in f]
288
289 solver=Nono(rows,cols)
290 print(solver)
291 for c in solver.columns:
292 c.step()
293 print()
294 print(solver)
295 for r in solver.rows:
296 r.step()
297 print()
298 print(solver)