Puzzling with Python

Every day I challenge myself to code something in Python. On those days when I don’t have students at The Coder School who would help me keep to my coding schedule, I have to find inspiration elsewhere. Sometimes I find Processing sketches to try to clone, or I go on programming sites like Hackerrank or CodeSignal. But there are a couple of accounts on Twitter who post puzzles that make great programming exercises. The puzzles from 1 to 9 Puzzle and Yohaku are intended to be solved by hand, and I wasn’t sure if they’d like to have their puzzles cracked by coders. But I’m happy the puzzle-creators are flattered that I come back often and find their puzzles so challenging to code.

Here’s an example:

puzzle190408.png

The directions are clear: place all the digits 1 to 9 (6 is already given in the center) so that the sums of the rows, columns and diagonals are the given values. What if we just took the remaining 8 numbers and placed them randomly in the remaining spaces, then check the row/column/diagonal totals? If any don’t work out, generate a new bunch of numbers, and so on. Sounds like an infinite loop! First we’ll import the random module to take random samples of our numbers, and the time module so we can measure how fast our solution is:

import random
import time

#save the starting time
start = time.time()

Now we need the list of numbers from 1 to 9. We’ll take out the one we were given in the middle, and assign the given totals of the rows, columns and diagonals.

#generate the numbers from 1 to 9
NUMS = list(range(1,10))

#the center number is given. This changes
e = 6
NUMS.remove(e)

#row, column, diagonal totals given. These change:
rows = [12,11,22]
cols = [14,16,15]
diagonals = [15,21] #down, up

Now we start an infinite loop where we’ll assign random numbers to the remaining spaces in the 3x3 grid, then go through the rows, columns and diagonals and check if there’s any error. “Continue” means “Start the loop over,” and if a total doesn’t add up, the numbers are wrong and we don’t need to check them any further.

#Infinite loop
while True:
    #randomly assign remaining numbers to letters
    a,b,c,d,f,g,h,i = random.sample(NUMS,8)
    #check if they work
    if a + b + c != rows[0]:
        continue #start the loop over
    if d + e + f != rows[1]:
        continue
    if g + h + i != rows[2]:
        continue
    if a + d + g != cols[0]:
        continue
    if b + e + h != cols[1]:
        continue
    if c + f + i != cols[2]:
        continue
    if a + e + i != diagonals[0]:
        continue
    if c + e + g != diagonals[1]:
        continue
    break

#print the solution:
print('{} {} {}'.format(a,b,c))
print('{} {} {}'.format(d,e,f))
print('{} {} {}'.format(g,h,i))

print("Time elapsed:",round(time.time() - start,1),"seconds")

Running this will give us the answer in a matter of seconds:

4 1 7
2 6 3
8 9 5
Time elapsed: 1.5 seconds

It works! Check the rows, columns and diagonals for yourself. Yes, all the coding and debugging took much longer than 1.5 seconds, but the good news is 1 to 9 Puzzle cycles through a handful of challenging types of puzzles, and another one of the same format will appear in a week or so. Like this one. It’s the same rules, it’s just the totals and the center number have changed:

puzzle190404.png

All you have to do is change the lists we wrote for the rows, columns and diagonals, and variable for the center number.

#the center number is given. This changes
e = 7
NUMS.remove(e)

#row, column, diagonal totals given. These change:
rows = [14,13,18]
cols = [18,14,13]
diagonals = [15,24] #down, up

The output should give the solution in no time.

5 1 8
4 7 2
9 6 3
Time elapsed: 1.1 seconds

So to solve the second (and third, and fourth…) puzzle, it only took us a minute or so of changing values in the original program. Talk about automating repetitious tasks! Now we can solve other puzzles!