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=''
 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')
   
+
+