Mercurial > hg > python
diff nono.py @ 71:eea4bcb657bc simple
Pruned back to pass 0
| author | Henry Thompson <ht@markup.co.uk> |
|---|---|
| date | Fri, 18 Jul 2025 21:54:39 +0100 |
| parents | 578cb569d815 |
| children | 15e540c190d5 |
line wrap: on
line diff
--- a/nono.py Sun Jun 04 16:24:20 2023 +0100 +++ b/nono.py Fri Jul 18 21:54:39 2025 +0100 @@ -9,10 +9,6 @@ eRed='[39m' RedFmt=Red+'%s'+eRed -def interleave(*args): - for vals in zip(*args): - yield from vals - class Run: '''What we're looking for, converted eventually into what we've found. @@ -96,297 +92,6 @@ def myPrintSize(self): return sum((len(str(run)) if isinstance(run,Run) else 1) for run in self.runs) -#=========v-old-v============ - - def resetAllRuns(self): - # compute the set of all possible layouts for runs - self.rn=len(self.runs) - rtot=sum(self.runs) - self.allRuns=list(self.seedList(0,0,self.margin0,self.marginN+1, - sum(1+self.runs[k] for k in range(self.rn))-1)) - self.nar=len(self.allRuns) - - def seedList(self,i,j,pos0,posN,runLen): - """ - :param i: starting skip before next run - :type i: 0 if pos==0 else 1 - :param j: next run number - :type j: int - :param pos0: left margin - :type pos0: int - :param posN: right margin - :type posN: int - :return: list of runlens separated, possibly prefixed, with skips as negative integers - """ - bound=posN-(pos0+runLen)+1 - dprint('s',i,j,pos0,posN,runLen,bound) - if j==self.rn: - yield [] - return - r=self.runs[j] - for v in range(i,bound): - for sub in self.seedList(1,j+1,pos0+v+r,posN,runLen-(r+1)): - yield [-v,r]+sub - - def step(self,pos): - if self.runs==[]: - return 0 - scratch=[0 if c.val is None else c.val for c in self] - wins=0 - changed=0 - for k,runs in enumerate(self.allRuns): - if (self.m.loop,self.cLet,pos,k)==(1,'R',7,5): - print(self.m) - dprint('=====pass %s.%s.%s.%s======'%(self.m.loop,self.cLet,pos,k)) - if self.onepass(self.margin0,self.marginN+1,scratch,runs.copy()): - wins+=1 - dprint(wins, scratch) - maybeSeq=None - inSeq=None - j=None - for i in range(self.n): - if scratch[i]>=wins: - # If blobby in _every_ pass, then must be a blob - if inSeq is None: - inSeq=i if maybeSeq is None else maybeSeq - dprint('s1',inSeq) - maybeSeq=None - if self[i].val is None: - dprint('s1a',i) - self.newBlob(i,True) # Check cross-vector - j=i - changed=1 - elif self[i].val is True: - pass - else: - eprint("Shouldn't happen: attempt to blob where x already present! %s at %s"%(self,i),err=101) - elif inSeq is not None: - if self[i].val is not True: - inSeq=None - maybeSeq=None - dprint('s2',i-1,j) - if j is not None: - # Only if we actually added a blob at some point - dpush('b_s2') - self.checkNew(i-1) - dpop('b_s2') - elif self[i].val is True and maybeSeq is None: - dprint('s3',i) - maybeSeq=i - dprint('s4',inSeq,i) - if inSeq is not None: - dpush('b_s4') - self.checkNew(i) - dpop('b_s4') - return changed - - def onepass(self,i0,iBound,scratch,stack): - """note that stack is not a simple run, but one with _negative_ numbers between - and possibly before the positive ones, indicating obligatory skips - """ - i=i0 # starting index into self/scratch/maybe - maybe=[0]*self.n - dprint('r: %s'%stack,i0,iBound) - req=sum((-r if r<0 else r) for r in stack) - while stack and i<iBound: - r=rr=stack.pop(0) - dprint('pop:',r) - if r<1: - # obligatory skip - # (Above init of self.allRuns is easier if we allow a 0 to be ignored - for k in range(i,i-r): - if self[k] is True: - # has to be False or None to skip - dprint('x0',k) - return False - i-=r - req+=r - r=rr=stack.pop(0) - # rr is run remaining -- how many we still need - # First, check if we can start here: 0 is OK, and n>0 iff n-1 is None or False - # if i>0 and self[i-1].val is True: - # dprint('x1',i) - # return - if (iBound-i)<req: - # Can't win, give up altogether - dprint('x2',i,iBound,req) - return False - j=i # start of run, if we find it - gapsFilled=0 - dprint('top:',j) - while rr>0: - # guaranteed by check above that we won't run over the far end - c=self[i].val - if c is False: - dprint('x3',i) - return False - if c is None: - # we could add a blob here - gapsFilled+=1 - # and a blob already present is OK - rr-=1 - i+=1 - # Maybe a win? - # look ahead, can we stop here? - if i<self.n and self[i].val is True: - dprint('x4',i) - return False -# elif gapsFilled==0: -# # We must have crossed at least one gap... -# print("Shouldn't happen: no gap! me:%s i:%s j:%s rr:%s"%(self,i, j, rr),file=sys.stderr) -# raise Exception - # Victory! - dprint('c6',r,j,i) - for k in range(j,i): - maybe[k]+=1 - req-=r - # on to the next run - # end of inner loop, did we win? - if (not stack) or i==iBound: - # yes - dprint('win:',maybe) - for k in range(iBound): - scratch[k]+=maybe[k] - return True - eprint("Shouldn't happen? - fell through",stack,i,iBound,err=102) - - def checkX(self,pos,crosspos): - # New x at pos, are there others that can be xed out now? - # Overkill as is? - dprint('cx',self.cLet+str(crosspos),pos) - if self.runs: - start0=self.margin0 # 0 if self.margin0==0 else self.margin0+1 - if self.marginal(range(start0,pos),self.runs[0]): - dprint('cx1a') - else: - dprint('cx1b') -# if len(self.runs)>1: - startN=self.marginN # self.marginN if (self.marginN==self.n-1) else self.marginN-1 - if self.marginal(range(startN,pos,-1),self.runs[-1]): - dprint('cx2a') - else: - dprint('cx2b') - - def checkNew(self,pos): - # New blob at pos, can we complete anything? - # Assuming @@ FTTB that it's never possible to definitively complete a - # non-marginal run, which is wrong if the length is unique and it's bounded... - start0=self.margin0 #0 if self.margin0==0 else self.margin0+1 - startN=self.marginN # self.marginN if (self.marginN==self.n-1) else self.marginN-1 - dprint('cn',start0,pos,startN) - changed=False - # First, find our boundaries: - s=pos # start index of our run - if s>start0: - while s>start0 and self[s-1].val is True: - s-=1 - f=pos # finish index of our run - if f<startN: - while f<startN and self[f+1].val is True: - f+=1 - l=(f-s)+1 # our length - dprint('%s:%s,%s,%s,%s:<%s>'%(self.cLet,s,f,l,self.runs,self)) - c=self.runs.count(l) - if c==0: - # not big enough yet - dprint('x0') - return - if self.runs[0]==l: - # is it safely left marginal, i.e. no blobs or big enough gaps before us? - if self.marginal(range(start0,s),l): - changed=True - dprint('n1') - # mark our margins - for i in range(start0,s): # not sure this is still needed since - # added gap-filling in self.marginal - if self[i].val is None: - self.newX(i,False) - if f<startN: - if self[f+1].val is None: - self.newX(f+1,True) - self.found0(f) # pull in the start margin at least to f+2 - else: - dprint('x1a') - if not changed: - if self.runs[-1]==l: - # is it safely _right_ marginal, i.e. no blobs or big enough gaps _after_ us? - if self.marginal(range(startN,f,-1),l): - changed=True - dprint('n2') - # mark our margins: still needed? see above - for i in range(startN,f,-1): - if self[i].val is None: - self.newX(i,False) - if s>start0: - if self[s-1].val is None: - self.newX(s-1,True) - self.foundN(s) # pull in the finish margin at least to s-2 - else: - dprint('x2a') - else: - dprint('x2b') - if changed: - self.resetAllRuns() - - def marginal(self,rng,l): - dprint('m%s'%self.cLet,rng.start,rng.stop,rng.step,l) - g=0 # length of a gap - for i in rng: - if self[i].val is True: - # Shouldn't be possible? - dprint('mx0') - return False - if self[i].val is False: - if g>0: - # Block a too-small gap - dprint('m1',i-g,i) - for j in range(i-g,i): - self.newX(j,True) - g=0 - else: - # None - g+=1 - if g==l: - # run could fit here, so no go - dprint('mx1') - return False - if g>0: - # Block a too-small gap - if rng.step==1: - # forward - dprint('m2f',i-g,i) - for j in range(i+1-g,i+1): - self.newX(j,True) - else: - # backward - dprint('m2b',i+g,i) - for j in range(i+g-1,i-1,-1): - self.newX(j,True) - return True - - def found0(self,i): - dprint('found0 called on %s at %s'%(self,i)) - i=self.margin0 - while self[i].val is False: - i+=1 - if self[i].val is True: - r=self.runs.pop(0) - self.initialComplete.append(r) - self.margin0+=r+1 - self.updateHeader(r=r,pre=True) - - def foundN(self,i): - dprint('foundN called on %s at %s'%(self,i)) - i=self.marginN - while self[i].val is False: - i-=1 - if self[i].val is True: - r=self.runs.pop() - self.finalComplete=[r]+self.finalComplete - self.marginN-=r+1 - self.updateHeader(r=r,pre=False) - - #=============new simple============ def pass0(self): # simple first pass down/across a row for i in range(0,len(self.runs),2): @@ -488,21 +193,6 @@ self.infix="" self.suffix="" - def newBlob(self,x,crossCheck=False): - self[x].setVal(True) - if crossCheck: - dpush('b_cc') - self[x].column.checkNew(self.y) - dpop('b_cc') - - def newX(self,x,crossCheck=False): - dprint('nx %s%s@%s'%('R',self.y,x)) - self[x].setVal(False) - if crossCheck: - dpush('x_cc') - self[x].column.checkX(self.y,x) - dpop('x_cc') - class Column(Vector): cLet='C' def __init__(self,n,m,pos): @@ -546,21 +236,6 @@ self.header=self.fmt%header dprint(self.header) - def newBlob(self,y,crossCheck=False): - self[y].setVal(True) - if crossCheck: - dpush('b_cc') - self[y].row.checkNew(self.x) - dpop('b_cc') - - def newX(self,y,crossCheck=False): - dprint('nx %s%s@%s'%('C',self.x,y)) - self[y].setVal(False) - if crossCheck: - dpush('x_cc') - self[y].row.checkX(self.x,y) - dpop('x_cc') - class Cell: def __init__(self,row,y,column,x): # At the intersection of row and column Vectors @@ -590,10 +265,11 @@ class Nono(dict): # 0,0 is upper left, so increasing y goes _downwards_, to match the standard layout - def __init__(self,runsPerRow,runsPerCol): + def __init__(self,runsPerRow,runsPerCol,debug): global SOLVER self.loop=0 - self.dp='' + self.dp='' # old depth hack + self.debug = debug self.dstate=[] SOLVER=self n=self.n=len(runsPerCol) @@ -621,71 +297,50 @@ r.updateHeader(maxWidth=maxRRwidth) self.rowfmt="%s|%%s|"%(' '*maxRRwidth) - def __str__(self): lines=[self.rowfmt%('|'.join([(self.columns[i]).header[j] for i in range(self.n)])) # 'rotate' for j in range(self.maxCRheight)] lines+=[str(r) for r in self.rows] return "\n".join(lines) -#=============new simple============ def pass0(self): # do columns of empty layout for c in self.columns: c.pass0() - print(self) + dprint('After Pass 0 for columns', self, sep = '\n') for r in self.rows: r.pass0() - print(self) + dprint('After Pass 0 for rows', self, sep = '\n') for c in self.columns: c.check0() - print(self) + dprint('After Check 0 for columns', self, sep = '\n') + dprint(self) for r in self.rows: r.check0() + dprint('After Check 0 for rows', self, sep = '\n') def solve(self): self.pass0() - print(self) - return - someChanged=1 - while someChanged>0: - self.loop+=1 - someChanged=0 - dprint("***Solve C %s***"%self.loop) - for c in self.columns: - someChanged+=c.step(c.x) - print(someChanged) - print(self) - dprint("***Solve R %s***"%self.loop) - for r in self.rows: - someChanged+=r.step(r.y) - print(someChanged) - print(self) -def dpush(s): - SOLVER.dp+=' ' - SOLVER.dstate.append(s) - -def dpop(s): - assert(SOLVER.dstate.pop()==s) - SOLVER.dp=SOLVER.dp[1:] - -def dprint(*args): - print(SOLVER.dp,end='') - print(*args) - sys.stdout.flush() +def dprint(*args, **kwargs): + '''Debug print''' + if SOLVER.debug: + print(*args, **kwargs) + sys.stdout.flush() def eprint(*args,**kw): + '''error print''' print(*args,file=sys.stderr) sys.stderr.flush() print(SOLVER) breakpoint() exit(kw['err']) -def wprint(*args): - print(*args,file=sys.stderr) - sys.stderr.flush() - if __name__ == '__main__': + if sys.argv[1] == '-d': + sys.argv.pop(1) + debug = True + else: + debug = False if len(sys.argv)>1: f=open(sys.argv[1]) else: @@ -698,6 +353,11 @@ cols=[[int(i) for i in v.split('.')] for v in vv[:n]] rows=[[int(i) for i in v.split('.')] for v in vv[n:]] - solver=Nono(rows,cols) + solver=Nono(rows, cols, debug) + print(solver) + print() solver.solve() + print('Done',solver, sep = '\n') + +
