How to make this faster

MRAB python at mrabarnett.plus.com
Fri Jul 5 18:25:54 CEST 2013


On 05/07/2013 16:17, Helmut Jarausch wrote:
> On Fri, 05 Jul 2013 15:45:25 +0100, Oscar Benjamin wrote:
>
>> Presumably then you're now down to the innermost loop as a bottle-neck:
>>
>>       Possibilities= 0
>>       for d in range(1,10) :
>>         if Row_Digits[r,d] or Col_Digits[c,d] or Sqr_Digits[Sq_No,d] : continue
>>         Possibilities+= 1
>>
>> If you make it so that e.g. Row_Digits[r] is a set of indices rather
>> than a list of bools then you can do this with something like
>>
>>     Possibilities = len(Row_Digits[r] | Col_Digits[c] | Sqr_Digits[Sq_No])
>>
>> or perhaps
>>
>>     Possibilities = len(set.union(Row_Digits[r], Col_Digits[c],
>> Sqr_Digits[Sq_No]))
>>
>> which I would expect to be a little faster than looping over range
>> since the loop is then performed under the hood by the builtin
>> set-type.
>>
>> It just takes practice.
>
> indeed
>
>> It's a little less obvious in Python than in
>> low-level languages where the bottlenecks will be and which operations
>> are faster/slower but optimisation always involves a certain amount of
>> trial and error anyway.
>>
>>
>> Oscar
>
> I've tried the following version
>
> def find_good_cell() :
>    Best= None
>    minPoss= 10
>    for r,c in Grid :
>      if  Grid[(r,c)] > 0 : continue
>      Sq_No= (r//3)*3+c//3
>      Possibilities= 9-len(Row_Digits[r] | Col_Digits[c] | Sqr_Digits[Sq_No])
>      if ( Possibilities < minPoss ) :
>        minPoss= Possibilities
>        Best= (r,c)
>
>    if minPoss == 0 : Best=(-1,-1)
>    return Best
>
> All_digits= set((1,2,3,4,5,6,7,8,9))
>
> def Solve(R_Cells) :
>    if  R_Cells == 0 :
>      print("\n\n++++++++++ S o l u t i o n ++++++++++\n")
>      Print_Grid()
>      return True
>
>    r,c= find_good_cell()
>    if r < 0 : return False
>    Sq_No= (r//3)*3+c//3
>
>    for d in All_digits - (Row_Digits[r] | Col_Digits[c] | Sqr_Digits[Sq_No]) :
>      # put d into Grid
>      Grid[(r,c)]= d
>      Row_Digits[r].add(d)
>      Col_Digits[c].add(d)
>      Sqr_Digits[Sq_No].add(d)
>
>      Success= Solve(R_Cells-1)
>
>      # remove d again
>      Grid[(r,c)]= 0
>      Row_Digits[r].remove(d)
>      Col_Digits[c].remove(d)
>      Sqr_Digits[Sq_No].remove(d)
>
>      if Success :
>        Zuege.append((d,r,c))
>        return True
>
>    return False
>
> which turns out to be as fast as the previous "dictionary only version".
> Probably,  set.remove is a bit slow
>
For comparison, here's my solution:

from collections import Counter

problem = '''
_________
_____3_85
__1_2____
___5_7___
__4___1__
_9_______
5______73
__2_1____
____4___9
'''

# Build the grid.
digits = "123456789"

grid = []

for row in problem.splitlines():
   if not row:
     continue

   new_row = []

   for cell in row:
     if cell.isdigit():
       new_row.append({cell})
     else:
       new_row.append(set(digits))

   grid.append(new_row)

# Solve the grid.
changed = True
while changed:
   changed = False

   # Look for cells that contain only one digit.
   for r in range(9):
     for c in range(9):
       if len(grid[r][c]) == 1:
         digit = list(grid[r][c])[0]

         # Remove from other cells in same row.
         for c2 in range(9):
           if c2 != c and digit in grid[r][c2]:
             grid[r][c2].remove(digit)
             changed = True

         # Remove from other cells in same column.
         for r2 in range(9):
           if r2 != r and digit in grid[r2][c]:
             grid[r2][c].remove(digit)
             changed = True

         # Remove from other cells in the same block of 9.
         start_row = r - r % 3
         start_column = c - c % 3
         for r2 in range(start_row, start_row + 3):
           for c2 in range(start_column, start_column + 3):
             if (r2, c2) != (r, c) and digit in grid[r2][c2]:
               grid[r2][c2].remove(digit)
               changed = True

   # Look for digits that occur in only one cell in a row.
   for r in range(9):
     counts = Counter()
     for c in range(9):
       counts += Counter(grid[r][c])

     unique = {digit for digit, times in counts.items() if times == 1}

     for c in range(9):
       if len(grid[r][c]) > 1 and len(grid[r][c] & unique) == 1:
         grid[r][c] &= unique
         changed = True

   # Look for digits that occur in only one cell in a column.
   for c in range(9):
     counts = Counter()
     for r in range(9):
       counts += Counter(grid[r][c])

     unique = {digit for digit, times in counts.items() if times == 1}

     for r in range(9):
       if len(grid[r][c]) > 1 and len(grid[r][c] & unique) == 1:
         grid[r][c] &= unique
         changed = True

   # Look for digits that occur in only one cell in a block of 9.
   for start_row in range(0, 9, 3):
     for start_column in range(0, 9, 3):
       counts = Counter()
       for r in range(start_row, start_row + 3):
         for c in range(start_column, start_column + 3):
           counts += Counter(grid[r][c])

       unique = {digit for digit, times in counts.items() if times == 1}

       for r in range(start_row, start_row + 3):
         for c in range(start_column, start_column + 3):
           if len(grid[r][c]) > 1 and len(grid[r][c] & unique) == 1:
             grid[r][c] &= unique
             changed = True

# Display the solution.
for row in grid:
   for cell in row:
     print("".join(sorted(cell)), end=" ")

   print()




More information about the Python-list mailing list