b ⇔ y c ⇔ x d ⇔ w e ⇔ v f ⇔ u g ⇔ t h ⇔ s i ⇔ r j ⇔ q k ⇔ p l ⇔ o m ⇔ n RubyConf 2016! • Letters swap alphabet position • Downcase and chunk into “words” of 5 characters i f y b x l m u 2 0 1 6 ⇒
◦ Eliminate each square that’s already attackable ◦ If a row/column has only one empty slot, place a queen • Branch to continue moving forward ◦ n Queens: pick an empty square and place a queen
• a unit has only one eligible slot for a needed value Then: • Set the slot • Remove that possibility from other slots in affected units A B C D E F G H I 1 2 3 4 5 6 7 8 9 9
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) def parse_grid(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.) def grid_values(grid): chars = [c for c in grid if c in digits or c in '0.'] assert len(chars) == 81 return dict(zip(squares, chars)) def display(values): width = 1+max(len(values[s]) for s in squares) line = '+'.join(['-'*(width*3)]*3) for r in rows: print ''.join(values[r+c].center(width)+('|' if c in '36' else '') for c in cols) if r in 'CF': print line print def assign(values, s, d): 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): if d not in values[s]: return values ## Already eliminated values[s] = values[s].replace(d,'') if len(values[s]) == 0: return False elif len(values[s]) == 1: d2 = values[s] if not all(eliminate(values, s2, d2) for s2 in peers[s]): return False 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 some(seq): for e in seq: if e: return e return False def search(values): if values is False: return False ## Failed earlier if all(len(values[s]) == 1 for s in squares): return values ## Solved! ## Chose the unfilled square s with the fewest possibilities n,s = min((len(values[s]), s) for s in squares if len(values[s]) > 1) return some(search(assign(values.copy(), s, d)) for d in values[s]) def solve(grid): return search(parse_grid(grid)) http://norvig.com/sudoku.html
= 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): if d not in values[s]: return values ## Already eliminated values[s] = values[s].replace(d,'') if len(values[s]) == 0: return False elif len(values[s]) == 1: d2 = values[s] if not all(eliminate(values, s2, d2) for s2 in peers[s]): return False 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 some(seq): for e in seq: if e: return e return False def search(values): if values is False: return False ## Failed earlier if all(len(values[s]) == 1 for s in squares): return values ## Solved! ## Chose the unfilled square s with the fewest possibilities n,s = min((len(values[s]), s) for s in squares if len(values[s]) > 1) return some(search(assign(values.copy(), s, d)) for d in values[s]) def solve(grid): return search(parse_grid(grid)) http://norvig.com/sudoku.html
list of how unknowns map to values or each other • Query it to see what unknowns resolve to concat_r([m, ? 1 , t], [? 2 ], [m, a, t, z]) ⇒ match ? 1 with a, and ? 2 with z
to unify through the substitution list • Outcomes: ◦ Initial values already match to the same known result ◦ Initial values match to known contradictory results ◦ Substitution chain ends with a still-unknown value
= walk(left), walk(right) # walk returns the object if there 3 extension = SubstitutionList.new # isn’t a substitution for it 4 5 if [left, right].all? { |e| e.kind_of?(Symbol) } && (left == right) 6 return extension # no changes needed, so it stays empty 7 end 8 9 if left.kind_of? Symbol # if either side is an unknown 10 extension[left] = right # attach it to a known value or 11 elsif right.kind_of? Symbol # chain it to another unknown 12 extension[right] = left 13 else 14 extension = left.class.unify(left, right, self) # defaults to == 15 end 16 extension 17 end
3 IdentityStore.new do 4 unify(left, right) 5 end 6 ] 7 end 1 def not_equal(left, right) 2 [ 3 IdentityStore.new do 4 disjoin(left, right) 5 end 6 ] 7 end