This was some nice parsing practice. As a beginner, this challenge has taught me I need to get better at RegEx expressions and the tool itself. My solution was very redundant using the formula tool, but hey, that's the beauty of Alteryx, it can be as simple or complex as you need to get the solution.
Never considered myself at the advanced level, so haven't done the second puzzle, but I'm pretty sure if I was good at Macros, I could alter my solution a tiny bit and solve that section too. Maybe one day soon!
I do truly believe that almost everything can be solved in Alteryx one way or another, but I will admit, that the intermediate and advanced puzzle was a too big nut for me to crack
Luckily Alteryx does support the usage of R or Python in this case, so that was my approach here.
Beginner & Intermediate
So the Beginner part was dead easy due to the "hidden" tool Make Columns, and I can never stop mentioning @NicoleJohnson for showing me this tool. Learn it, like it, love it -you find this tool under the Laboratory tab.
Now for the second part the Intermediate, to solve the first beginner puzzle, I immediately thought of using Python, event thought I am not a Python developer, but it seem logic to me, to be a thing thousands had done before in Python.
My initial search brought me to this: Shortest Sudoku Solver in Python - How does it work? on Stack Overflow, and all though the explaining was thoroughly, I didn't make it work as intended, when trying to adapt this into Alteryx
So I researched a little bit further, and found this very nice tutorial Solving Every Sudoku Puzzle by Peter Norvig. Based on this example, I was able to adapt the logic and put it into Alteryx Python component
#################################
# List all non-standard packages to be imported by your
# script here (only missing packages will be installed)
from ayx import Package
Package.installPackages(['pandas','numpy'])
#################################
from ayx import Alteryx
df = Alteryx.read("#1")
#print(df.iloc[0,0])
puzzle = df.iloc[0,0]
#################################
## Solve Every Sudoku Puzzle
## See http://norvig.com/sudoku.html
def cross(A, B):
"Cross product of elements in A and elements in B."
return [a+b for a in A for b in B]
digits = '123456789'
rows = 'ABCDEFGHI'
cols = digits
squares = cross(rows, cols)
unitlist = ([cross(rows, c) for c in cols] +
[cross(r, cols) for r in rows] +
[cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')])
units = dict((s, [u for u in unitlist if s in u])
for s in squares)
peers = dict((s, set(sum(units[s],[]))-set([s]))
for s in squares)
################ Parse a Grid ################
def parse_grid(grid):
"""Convert grid to a dict of possible values, {square: digits}, or
return False if a contradiction is detected."""
## To start, every square can be any digit; then assign values from the grid.
values = dict((s, digits) for s in squares)
for s,d in grid_values(grid).items():
if d in digits and not assign(values, s, d):
return False ## (Fail if we can't assign d to square s.)
return values
def grid_values(grid):
"Convert grid into a dict of {square: char} with '0' or '.' for empties."
chars = [c for c in grid if c in digits or c in '0.']
assert len(chars) == 81
return dict(zip(squares, chars))
################ Constraint Propagation ################
def assign(values, s, d):
"""Eliminate all the other values (except d) from values[s] and propagate.
Return values, except return False if a contradiction is detected."""
other_values = values[s].replace(d, '')
if all(eliminate(values, s, d2) for d2 in other_values):
return values
else:
return False
def eliminate(values, s, d):
"""Eliminate d from values[s]; propagate when values or places <= 2.
Return values, except return False if a contradiction is detected."""
if d not in values[s]:
return values ## Already eliminated
values[s] = values[s].replace(d,'')
## (1) If a square s is reduced to one value d2, then eliminate d2 from the peers.
if len(values[s]) == 0:
return False ## Contradiction: removed last value
elif len(values[s]) == 1:
d2 = values[s]
if not all(eliminate(values, s2, d2) for s2 in peers[s]):
return False
## (2) If a unit u is reduced to only one place for a value d, then put it there.
for u in units[s]:
dplaces = [s for s in u if d in values[s]]
if len(dplaces) == 0:
return False ## Contradiction: no place for this value
elif len(dplaces) == 1:
# d can only be in one place in unit; assign it there
if not assign(values, dplaces[0], d):
return False
return values
def string_return(values):
return "".join(values.values())
#################################
solved = string_return(parse_grid(puzzle))
#print(solved)
#print("Dataframe" , df, sep='\n')
#df.loc[1] = [solved]
df.insert(1,"solutions",[solved], True)
#print("Dataframe" , df, sep='\n')
Alteryx.write(df,1)
#################################
It was not my first rodeo with Alteryx and Python, and for beginners I can highly recommend @SydneyF excellent Tool Mastery on Python
One of the key things to know, is that when using the Python tool input and output is done in Pandas Data Frame, but once you get at hang of this, it's no biggie.
So if you examine my code (I did mention I am not a Python developer right?) you can see I do some extraction of the puzzle string and in the end converting the result back into a string I then append to the Pandas Data Frame and output it again,
It this should have been even more nicer, I should have made the logic so you actually could parse multiple puzzles at the same time, and the iterate over them, but oh well - there is always room for improvement.
So using Python did solve the Beginner puzzle okay fast Python Execution Time
So the Advanced solution was just to use the same Python code again, and yes, I should obviously have encapsuled this in a macro, since I am using the same code more than once, but apparently I am a slow learner 🙂🙂
Advanced Solution And apparently, the Python code is not that much longer in solving this harder puzzle Advanced Python Execution Time
So If you have read so far, I hope you enjoyed this explanation.