Mercurial > hg > python
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nono.py Mon Mar 09 14:58:04 2020 +0000 @@ -0,0 +1,298 @@ +#!/usr/bin/python3 +# Expects e.g. ^A copy from Nonograms dprint preview cols, then blank line, then rows +# rows are space-separated +# cols are one-digit-after-another, unless some 2-digit, in which case x is separator +# E.g. +# 13x1x2 +# 19 +# maps to +# 13 +# 1 1 +# 2 9 + +import sys + +Red='[31m' +eRed='[39m' +RedFmt=Red+'%s'+eRed + +def interleave(*args): + for vals in zip(*args): + yield from vals + +class Vector(list): + # reads top-to-bottom or left-to-right + def __init__(self,n,m,runs): + list.__init__(self,list(range(n))) + self.n=n + self.runs=runs + # 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,0, + sum(1+self.runs[k] for k in range(self.rn))-1)) + self.nar=len(self.allRuns) + + def seedList(self,i,j,pos,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 pos: left margin + :type pos: int + """ + bound=self.n-(pos+runLen)+1 + #dprint('s',i,j,pos,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,pos+v+r,runLen-(r+1)): + yield [-v,r]+sub + + def __repr__(self): + return "V@%s%s:%s"%(self.x,self.runs,list.__repr__(self)) + + def __str__(self): + return '%s|'%('|'.join(str(c) for c in self)) + + def step(self): + scratch=[0 if c.val is None else c.val for c in self] + for k,runs in enumerate(self.allRuns): + dprint('=====pass %s======'%k) + self.onepass(0,self.n,scratch,runs.copy()) + dprint(scratch) + for i in range(self.n): + if scratch[i]==self.nar: + # If blobby in _every_ pass, then must be a blob + if self[i].val is None: + self[i].setVal(True) + elif self[i].val is True: + # already there + pass + else: + print("Shouldn't happen: attempt to blob where x already present! %s at %s"%(self,i),file=sys.stderr) + exit(101) + + 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 + j=-1 # index into run + maybe=[0]*iBound + dprint('r: %s'%stack) + 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 + i-=r + req+=r + r=rr=stack.pop(0) + # rr is run remaining -- how many we still need + j+=1 # index of current run in self.runs, we'll need to decorate that eventually + inOne=-1 # if non-neg, records the start point of a possible run + gapsFilled=0 + # First, check if we can start here: 0 is OK, and n>0 iff n-1 is None or False + if i>0 and i<iBound: + while self[i-1].val: + i+=1 + if (iBound-i)<req: + # Can't win, give up altogether + dprint('c0',i,iBound,req) + return + while i<iBound: + c=self[i].val + dprint('top',i,c,inOne,rr) + if c is None: + # we could add a blob here + dprint('c1') + gapsFilled+=1 + rr-=1 + if inOne<0: + dprint('c1a',i) + # starts here + inOne=i + # fall through to check for completion + else: + dprint('c2') + # c is a bool + if inOne<0: + dprint('c2a') + if c: + dprint('c2a1') + # a *, we can possible start something here + inOne=i + rr-=1 + # fall through to check for completion + else: + dprint('c2a2') + # an x, can't start here, just move along + i+=1 + continue + else: + dprint('c2b') + if c: + dprint('c2b1') + # a blob, extend or complete a partial + rr-=1 + # fall through to check for completion + else: + # abandon a partial + dprint('c2b2') + inOne=-1 + rr=r + i+=1 + continue + if rr>0: + dprint('c3') + # we're not done, carry on + i+=1 + continue + # Maybe a win? + # look ahead, can we stop here? + # NB _self_.n + if i+1<self.n and self[i+1].val: + dprint('c4') + # Nope + inOne=-1 + rr=r + gapsFilled=0 + i+=1 + continue + elif gapsFilled==0: + dprint('c5') + # We must have crossed at least on gap... + print("Shouldn't happen: no gap! me:%s i:%s j:%s rr:%s inOne:%s"%(self,i, j, rr, inOne),file=sys.stderr) + exit(100) + # Victory! + dprint('c6',r,inOne,i) + for k in range(inOne,i+1): + maybe[k]+=1 + i+=1 + req-=r + break + # 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] + +class Row(Vector): + def __init__(self,n,m,runs,pos,dprintWidth): + Vector.__init__(self,n,m,runs) + self.y=pos + self.dprintWidth=dprintWidth + self.fmt="%%%ss|"%dprintWidth + + def __str__(self): + return ((self.fmt%(' '.join(str(r) for r in self.runs)))+ + Vector.__str__(self)) + +class Column(Vector): + def __init__(self,n,m,runs,pos,dprintHeight): + Vector.__init__(self,n,m,runs) + self.x=pos + self.dprintHeight=dprintHeight + self.fmt="%%%ss"%self.dprintHeight + self.updateHeader() + + def updateHeader(self): + header=('-'.join(str(c) for c in self.runs)) + self.header=self.fmt%header # pad to same 'height' + +class Cell: + def __init__(self,row,y,column,x): + # At the intersection of row and column Vectors + self.row=row + self.column=column + self.x=x + self.y=y + self.val=None # three valued: None(unknown), True(filled), False(empty) + self.row[x]=self + self.column[y]=self + + def __repr__(self): + return "C@(%s,%s):%s"%(self.x,self.y,self.val) + + def __str__(self): + return ' ' if self.val is None else ('\u25A0' if self.val else 'x') + + def setVal(self,v): + if v is True: + if self.val is False: + dprint("Warning: x -> * at %s,%s"%(self.x,self.y)) + elif self.val is True: + # No-op + return + # @@ check row/col completed + else: + if self.val is not None: + dprint("Warning: %s -> %s at %s,%s"%(self.val,v,self.x,self.y)) + self.val=v + +class Nono(dict): + # 0,0 is upper left, so increasing y goes _downwards_, to match the standard layout + def __init__(self,rows,cols): + n=self.n=len(cols) + if n!=len(rows): + print("losing r:%s x c:%s"%(len(rows),n),sys.stderr) + exit(1) + self.rc=rows + rowDprintWidth=max(sum(len(str(r)) for r in row)+len(row)-1 for row in rows) + self.rowfmt="%s|%%s"%(' '*rowDprintWidth) + self.cc=cols + # dprint col nums>9 vertically :-( + self.colDprintHeight=max(sum(len(str(c)) for c in col)+len(col)-1 for col in cols) + self.columns=cc=[Column(n,self,cols[i],i,self.colDprintHeight) for i in range(20)] + self.rows=rr=[Row(n,self,rows[i],i,rowDprintWidth) for i in range(20)] + for x in range(20): + for y in range(20): + self[(x,y)]=Cell(rr[y],y,cc[x],x) + + def __str__(self): + lines=[self.rowfmt%('|'.join([(self.columns[i]).header[j] for i in range(self.n)])) # 'rotate' + for j in range(self.colDprintHeight)] + lines+=[str(r) for r in self.rows] + return "\n".join(lines) + +def dprint(*args): + pass + +if __name__ == '__main__': + if len(sys.argv)>1: + f=open(sys.argv[1]) + else: + f=sys.stdin + + cols=[] + + for l in f: + l=l.rstrip() + if l=='': + break + if 'x' in l: + vv=[int(s) for s in l.split('x')] + else: + vv=[int(c) for c in l] + cols.append(vv) + + rows=[[int(s) for s in l.split()] for l in f] + + solver=Nono(rows,cols) + print(solver) + for c in solver.columns: + c.step() + print() + print(solver) + for r in solver.rows: + r.step() + print() + print(solver)