Answer To: Q4,5,6
Swapnil answered on Dec 09 2021
x/.ipynb_checkpoints/Final Notebook-checkpoint.ipynb{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Before you turn this problem in, make sure everything runs as expected. First, **restart the kernel** (in the menubar, select Kernel$\\rightarrow$Restart) and then **run all cells** (in the menubar, select Cell$\\rightarrow$Run All).\n",
"\n",
"Make sure you fill in any place that says `YOUR CODE HERE` or \"YOUR ANSWER HERE\", as well as your name and collaborators below:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"NAME = \"\"\n",
"COLLABORATORS = \"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Homework 12: Sudoku\n",
"\n",
"Copyright Luca de Alfaro, 2019-20. License: CC-BY-NC-ND."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Submission\n",
"\n",
"Please submit to this Google Form.\n",
"\n",
"Deadline: Tuesday December 8, 11pm (check on Canvas for updated information)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test Format\n",
"\n",
"There are 6 questions, but not all of them require you to write code:\n",
"\n",
"- Question 1 asks you to complete the code for propagating a single cell.\n",
"- Question 2 asks you to write the code to propagate all cells.\n",
"- For Question 3, you just need to copy your solution to Question 2 in another place.\n",
"- Question 4 asks you to implement a helper function that detects elements that occur in only one of a list of sets.\n",
"- Question 5 asks you to implement the where can it go heuristic.\n",
"- Question 6 simply checks that your code is efficient; you don't need to write any code.\n",
"\n",
"There are a total of 70 points."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let us write a Sudoku solver. We want to get as input a Sudoku with some cells filled with values, and we want to get as output a solution, if one exists, and otherwise a notice that the input Sudoku puzzle has no solutions.\n",
"\n",
"You will wonder, why spend so much time on Sudoku?\n",
"\n",
"For two reasons.\n",
"\n",
"First, the way we go about solving Sudoku is prototypical of a very large number of problems in computer science. In these problems, the solution is attained through a mix of search (we attempt to fill a square with a number and see if it works out), and constraint propagation (if we fill a square with, say, a 1, then there can be no 1's in the same row, column, and 3x3 square).\n",
"\n",
"Second, and related, the way we go about solving Sudoku puzzles is closely related to how SAT solvers work. So closely related, in fact, that while we describe for you how a Sudoku solver works, you will have to write a SAT solver as exercise."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sudoku representation\n",
"\n",
"First, let us do some grunt work and define a representation for a Sudoku problem.\n",
"\n",
"One initial idea would be to represent a Sudoku problem via a 9×9 matrix, where each entry can be either a digit from 1 to 9, or 0 to signify \"blank\". This would work in some sense, but it would not be a very useful representation. If you have solved Sudoku by hand (and if you have not, please go and solve a couple; it will teach you a lot about what we need to do), you will know that the following strategy works:\n",
"\n",
"Repeat:\n",
"\n",
"- Look at all blank spaces. Can you find one where only one digit fits? If so, write the digit there.\n",
"- If you cannot find any blank space as above, try to find one where only a couple or so digits can fit. Try putting in one of those digits, and see if you can solve the puzzle with that choice. If not, backtrack, and try another digit.\n",
"\n",
"Thus, it will be very useful to us to remember not only the known digits, but also, which digits can fit into any blank space. Hence, we represent a Sudoku problem via a 9×9 matrix of sets: each set contains the digits that can fit in a given space. Of course, a known digit is just a set containing only one element. We will solve a Sudoku problem by progressively \"shrinking\" these sets of possibilities, until they all contain exactly one element.\n",
"\n",
"Let us write some code that enables us to define a Sudoku problem, and display it for us; this will be very useful both for our fun and for debugging."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, though, let's write a tiny helper function that returns the only element from a singleton set."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"def getel(s):\n",
" assert len(s) == 1\n",
" return list(s)[0]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"class Sudoku(object):\n",
" def __init__(self, elements):\n",
" if isinstance(elements, Sudoku):\n",
" # We let self.m consist of copies of each set in elements.m\n",
" self.m = [[x.copy() for x in row] for row in elements.m]\n",
" else:\n",
" assert len(elements) == 9\n",
" for s in elements:\n",
" assert len(s) == 9\n",
" # We let self.m be our Sudoku problem, a 9x9 matrix of sets.\n",
" self.m = []\n",
" for s in elements:\n",
" row = []\n",
" for c in s:\n",
" if isinstance(c, str):\n",
" if c.isdigit():\n",
" row.append({int(c)})\n",
" else:\n",
" row.append({1, 2, 3, 4, 5, 6, 7, 8, 9})\n",
" else:\n",
" assert isinstance(c, set)\n",
" row.append(c)\n",
" self.m.append(row)\n",
" def show(self, details=False):\n",
" if details:\n",
" print(\"+-----------------------------+-----------------------------+-----------------------------+\")\n",
" for i in range(9):\n",
" r = '|'\n",
" for j in range(9):\n",
" # We represent the set {2, 3, 5} via _23_5____\n",
" s = ''\n",
" for k in range(1, 10):\n",
" s += str(k) if k in self.m[i][j] else '_'\n",
" r += s\n",
" r += '|' if (j + 1) % 3 == 0 else ' '\n",
" print(r)\n",
" if (i + 1) % 3 == 0:\n",
" print(\"+-----------------------------+-----------------------------+-----------------------------+\")\n",
" else:\n",
" print(\"+---+---+---+\")\n",
" for i in range(9):\n",
" r = '|'\n",
" for j in range(9):\n",
" if len(self.m[i][j]) == 1:\n",
" r += str(getel(self.m[i][j]))\n",
" else:\n",
" r += \".\"\n",
" if (j + 1) % 3 == 0:\n",
" r += \"|\"\n",
" print(r)\n",
" if (i + 1) % 3 == 0:\n",
" print(\"+---+---+---+\")\n",
" def to_string(self):\n",
" as_lists = [[list(self.m[i][j]) for j in range(9)] for i in range(9)]\n",
" return json.dumps(as_lists)\n",
" @staticmethod\n",
" def from_string(s):\n",
" as_lists = json.loads(s)\n",
" as_sets = [[set(el) for el in row] for row in as_lists]\n",
" return Sudoku(as_sets)\n",
" def __eq__(self, other):\n",
" return self.m == other.m\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" from nose.tools import assert_equal, assert_true\n",
" from nose.tools import assert_false, assert_almost_equal\n",
"except:\n",
" !pip install nose\n",
" from nose.tools import assert_equal, assert_true\n",
" from nose.tools import assert_false, assert_almost_equal"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|419|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 123456789|123456789 ______7__ 123456789|123456789 123456789 123456789|\n",
"|_____6___ 123456789 123456789|1________ ________9 ____5____|123456789 123456789 123456789|\n",
"|123456789 ________9 _______8_|123456789 123456789 123456789|123456789 _____6___ 123456789|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 123456789 123456789|123456789 _____6___ 123456789|123456789 123456789 __3______|\n",
"|___4_____ 123456789 123456789|_______8_ 123456789 __3______|123456789 123456789 1________|\n",
"|______7__ 123456789 123456789|123456789 _2_______ 123456789|123456789 123456789 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|123456789 _____6___ 123456789|123456789 123456789 123456789|_2_______ _______8_ 123456789|\n",
"|123456789 123456789 123456789|___4_____ 1________ ________9|123456789 123456789 ____5____|\n",
"|123456789 123456789 123456789|123456789 _______8_ 123456789|123456789 ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 123456789|123456789 ______7__ 123456789|123456789 123456789 123456789|\n",
"|_____6___ 123456789 123456789|1________ ________9 ____5____|123456789 123456789 123456789|\n",
"|123456789 ________9 _______8_|123456789 123456789 123456789|123456789 _____6___ 123456789|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 123456789 123456789|123456789 _____6___ 123456789|123456789 123456789 __3______|\n",
"|___4_____ 123456789 123456789|_______8_ 123456789 __3______|123456789 123456789 1________|\n",
"|______7__ 123456789 123456789|123456789 _2_______ 123456789|123456789 123456789 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|123456789 _____6___ 123456789|123456789 123456789 123456789|_2_______ _______8_ 123456789|\n",
"|123456789 123456789 123456789|___4_____ 1________ ________9|123456789 123456789 ____5____|\n",
"|123456789 123456789 123456789|123456789 _______8_ 123456789|123456789 ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"from nose.tools import assert_equal\n",
"sd = Sudoku([\n",
" '53__7____',\n",
" '6__195___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___419__5',\n",
" '____8__79'\n",
"])\n",
"sd.show()\n",
"sd.show(details=True)\n",
"s = sd.to_string()\n",
"sdd = Sudoku.from_string(s)\n",
"sdd.show(details=True)\n",
"assert_equal(sd, sdd)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"sd1 = Sudoku(sd)\n",
"assert_equal(sd, sd1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Constraint propagation\n",
"\n",
"When the set in a Sudoku cell contains only one element, this means that the digit at that cell is known. We can then propagate the knowledge, ruling out that digit in the same row, in the same column, and in the same 3x3 cell.\n",
"\n",
"We first write a method that propagates the constraint from a single cell. The method will return the list of newly-determined cells, that is, the list of cells who also now (but not before) are associated with a 1-element set. This is useful, because we can then propagate the constraints from those cells in turn. Further, if an empty set is ever generated, we raise the exception Unsolvable: this means that there is no solution to the proposed Sudoku puzzle.\n",
"\n",
"We don't want to steal all the fun from you; thus, we will give you the main pieces of the implemenetation, but we ask you to fill in the blanks. We provide tests so you can catch any errors right away.\n",
"\n",
"## Question 1: Propagating a single cell"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"class Unsolvable(Exception):\n",
" pass\n",
"def sudoku_ruleout(self, i, j, x):\n",
" \"\"\"The input consists in a cell (i, j), and a value x.\n",
" The function removes x from the set self.m[i][j] at the cell, if present, and:\n",
" - if the result is empty, raises Unsolvable;\n",
" - if the cell used to be a non-singleton cell and is now a singleton\n",
" cell, then returns the set {(i, j)};\n",
" - otherwise, returns the empty set.\"\"\"\n",
" c = self.m[i][j]\n",
" n = len(c)\n",
" c.discard(x)\n",
" self.m[i][j] = c\n",
" if len(c) == 0:\n",
" raise Unsolvable()\n",
" return {(i, j)} if 1 == len(c) < n else set()\n",
"Sudoku.ruleout = sudoku_ruleout"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The method propagate_cell(ij) takes as input a pair ij of coordinates. If the set of possible digits self.m[i][j] for cell i,j contains more than one digit, then no propagation is done. If the set contains a single digit x, then we:\n",
"\n",
"- Remove x from the sets of all other cells on the same row, column, and 3x3 block.\n",
"- Collect all the newly singleton cells that are formed, due to the digit x being removed, and we return them as a set.\n",
"\n",
"We give you an implementation that takes care of removing x from the same row, and we ask you to complete the implementation to take care of the column and 3x3 block as well."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_propagate_cell(self, ij):\n",
" i, j = ij\n",
" if len(self.m[i][j]) > 1:\n",
" # Nothing to propagate from cell (i,j).\n",
" return set()\n",
" # We keep track of the newly-singleton cells.\n",
" newly_singleton = set()\n",
" x = getel(self.m[i][j]) # Value at (i, j).\n",
" # Same row.\n",
" for jj in range(9):\n",
" if jj != j: # Do not propagate to the element itself.\n",
" newly_singleton.update(self.ruleout(i, jj, x))\n",
" # Same column.\n",
" # YOUR CODE HERE\n",
" for ii in range(9):\n",
" if ii != i:\n",
" newly_singleton.update(self.ruleout(ii, j, x))\n",
" # Same block of 3x3 cells.\n",
" # YOUR CODE HERE\n",
" current_i, current_j = i // 3, j // 3\n",
" current_i, current_j = 3*current_i + 1, 3*current_j + 1\n",
" \n",
" for di in range(-1, 2):\n",
" for dj in range(-1, 2):\n",
" ni, nj = current_i + di, current_j + dj\n",
" if i != ni and j != nj:\n",
" newly_singleton.update(self.ruleout(ni, nj, x))\n",
" # Returns the list of newly-singleton cells.\n",
" return newly_singleton\n",
"\n",
"Sudoku.propagate_cell = sudoku_propagate_cell"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ _2_______|_____6___ ______7__ _______8_|________9 12_4_____ _2_______|\n",
"|_____6___ ______7__ 12_4__7__|123______ ________9 ____5____|__3______ 12_4_____ _______8_|\n",
"|12_______ ________9 _______8_|__3______ ___4_____ 12_______|____5____ _____6___ ______7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ ____5____ ________9|1_____7_9 _____6___ 1__4__7_9|___4_____ _2_______ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1________ __3______|________9 _2_______ ___4_____|_______8_ ____5____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 _______8_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|__3______ ___4_____ _2345____|_2__56___ _______8_ _____6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"Good! It was unsolvable.\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ _2_______|_____6___ ______7__ _______8_|________9 12_4_____ _23______|\n",
"|_____6___ ______7__ 12_4__7__|123______ ________9 ____5____|__3______ 12_4_____ _______8_|\n",
"|12_______ ________9 _______8_|__3______ ___4_____ 12_______|____5____ _____6___ ______7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ ____5____ ________9|1_____7_9 _____6___ 1__4__7_9|___4_____ _2_______ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1________ __3______|________9 _2_______ ___4_____|_______8_ ____5____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 _______8_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|__3______ ___4_____ _2345____|_2__56___ _______8_ _____6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"tsd = Sudoku.from_string('[[[5], [3], [2], [6], [7], [8], [9], [1, 2, 4], [2]], [[6], [7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3], [1, 2, 4], [8]], [[1, 2], [9], [8], [3], [4], [1, 2], [5], [6], [7]], [[8], [5], [9], [1, 9, 7], [6], [1, 4, 9, 7], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[1, 9], [6], [1, 5, 9, 7], [9, 5, 7], [3], [9, 7], [2], [8], [4]], [[9, 2], [8], [9, 2, 7], [4], [1], [9, 2, 7], [6], [3], [5]], [[3], [4], [2, 3, 4, 5], [2, 5, 6], [8], [6], [1], [7], [9]]]')\n",
"tsd.show(details=True)\n",
"try:\n",
" tsd.propagate_cell((0, 2))\n",
"except Unsolvable:\n",
" print(\"Good! It was unsolvable.\")\n",
"else:\n",
" raise Exception(\"Hey, it was unsolvable\")\n",
"\n",
"tsd = Sudoku.from_string('[[[5], [3], [2], [6], [7], [8], [9], [1, 2, 4], [2, 3]], [[6], [7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3], [1, 2, 4], [8]], [[1, 2], [9], [8], [3], [4], [1, 2], [5], [6], [7]], [[8], [5], [9], [1, 9, 7], [6], [1, 4, 9, 7], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[1, 9], [6], [1, 5, 9, 7], [9, 5, 7], [3], [9, 7], [2], [8], [4]], [[9, 2], [8], [9, 2, 7], [4], [1], [9, 2, 7], [6], [3], [5]], [[3], [4], [2, 3, 4, 5], [2, 5, 6], [8], [6], [1], [7], [9]]]')\n",
"tsd.show(details=True)\n",
"assert_equal(tsd.propagate_cell((0, 2)), {(0, 8), (2, 0)})\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Propagating all cells, once"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The simplest thing we can do is propagate each cell, once."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_propagate_all_cells_once(self):\n",
" for i in range(9):\n",
" for j in range(9):\n",
" self.propagate_cell((i, j))\n",
"\n",
"Sudoku.propagate_all_cells_once = sudoku_propagate_all_cells_once"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|419|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|853|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|..7|284|\n",
"|...|419|.35|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|_2___6___ ______7__ _2_4_6_8_|1__4___89 12_4____9 _2_4___8_|\n",
"|_____6___ _2_4__7__ _2_4__7__|1________ ________9 ____5____|__34__78_ _234_____ _2_4__78_|\n",
"|12_______ ________9 _______8_|_23______ __34_____ _2_4_____|1_345_7__ _____6___ _2_4__7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 12__5____ 12__5___9|____5_7_9 _____6___ 1__4__7__|___45_7_9 _2_45___9 __3______|\n",
"|___4_____ _2__5____ _2__56__9|_______8_ ____5____ __3______|____5_7_9 _2__5___9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|____5___9 _2_______ 1__4_____|___45__89 ___45___9 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_3_____9 _____6___ 1_345_7_9|__3_5_7__ __3_5____ ______7__|_2_______ _______8_ ___4_____|\n",
"|_23______ _2____78_ _23___7__|___4_____ 1________ ________9|__3__6___ __3______ ____5____|\n",
"|123______ 12_45____ 12345____|_23_56___ _______8_ _2___6___|1_34_6___ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6__195___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___419__5',\n",
" '____8__79'\n",
"])\n",
"sd.show()\n",
"sd.propagate_all_cells_once()\n",
"sd.show()\n",
"sd.show(details=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Question 2: Propagating all cells, repeatedly\n",
"\n",
"This is a good beginning, but it's not quite enough. As we propagate the constraints, cells that did not use to be singletons may have become singletons. For eample, in the above example, the center cell has become known to be a 5: we need to make sure that also these singletons are propagated.\n",
"\n",
"This is why we have written propagate_cell so that it returns the set of newly-singleton cells.\n",
"We need now to write a method full_propagation that at the beginning starts with a set of to_propagate cells (if it is not specified, then we just take it to consist of all singleton cells). Then, it picks a cell from the to_propagate set, and propagates from it, adding any newly singleton cell to to_propagate. Once there are no more cells to be propagated, the method returns. If this sounds similar to graph reachability, it is ... because it is! It is once again the algorithmic pattern of keeping a list of work to be done, then iteratively picking an element from the list, doing it, possibly updating the list of work to be done with new work that has to be done as a result of what we just did, and continuing in this fashion until there is nothing left to do. We will let you write this function. The portion you have to write can be done in three lines of code."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_full_propagation(self, to_propagate=None):\n",
" if to_propagate is None:\n",
" to_propagate = {(i, j) for i in range(9) for j in range(9)}\n",
" # This code is the (A) code; will be referenced later.\n",
" # YOUR CODE HERE\n",
" for (i, j) in to_propagate:\n",
" x = self.propagate_cell((i, j))\n",
" sudoku_full_propagation(self, x)\n",
"\n",
"Sudoku.full_propagation = sudoku_full_propagation\n"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ ___4_____|_____6___ ______7__ _______8_|________9 1________ _2_______|\n",
"|_____6___ ______7__ _2_______|1________ ________9 ____5____|__3______ ___4_____ _______8_|\n",
"|1________ ________9 _______8_|__3______ ___4_____ _2_______|____5____ _____6___ ______7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ ____5____ ________9|______7__ _____6___ 1________|___4_____ _2_______ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1________ __3______|________9 _2_______ ___4_____|_______8_ ____5____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|________9 _____6___ 1________|____5____ __3______ ______7__|_2_______ _______8_ ___4_____|\n",
"|_2_______ _______8_ ______7__|___4_____ 1________ ________9|_____6___ __3______ ____5____|\n",
"|__3______ ___4_____ ____5____|_2_______ _______8_ _____6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6__195___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___419__5',\n",
" '____8__79'\n",
"])\n",
"sd.full_propagation()\n",
"sd.show(details=True)\n",
"sdd = Sudoku.from_string('[[[5], [3], [4], [6], [7], [8], [9], [1], [2]], [[6], [7], [2], [1], [9], [5], [3], [4], [8]], [[1], [9], [8], [3], [4], [2], [5], [6], [7]], [[8], [5], [9], [7], [6], [1], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[9], [6], [1], [5], [3], [7], [2], [8], [4]], [[2], [8], [7], [4], [1], [9], [6], [3], [5]], [[3], [4], [5], [2], [8], [6], [1], [7], [9]]]')\n",
"assert_equal(sd, sdd)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Searching for a solution\n",
"\n",
"Many Sudoku problems can be solved entirely by constraint propagation.\n",
"They are designed to be so: they are designed to be relatively easy, so that humans can solve them while on a lounge chair at the beach -- I know this from personal experience!\n",
"\n",
"But it is by no means necessary that this is true. If we create more complex problems, or less determined problems, constraint propagation no longer suffices. As a simple example, let's just blank some cells in the previous problem, and run full propagation again:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|.95|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|41.|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|12___6___ ______7__ 12___6_8_|___4___89 12_4_____ _2_____8_|\n",
"|_____6___ 1__4__7__ 12_4__7__|123______ ________9 ____5____|__34___8_ 12_4_____ _2____78_|\n",
"|12_______ ________9 _______8_|123______ ___4_____ 12_______|__3_5____ _____6___ _2____7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 1___5____ 1___5___9|1_____7_9 _____6___ 1__4__7_9|___45____ _2_45____ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|1_______9 _2_______ 1__4____9|___45__8_ ___45____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 ______78_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|_23______ ___45____ _2345____|_2__56___ _______8_ _2___6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6___95___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___41___5',\n",
" '____8__79'\n",
"])\n",
"sd.show()\n",
"sd.full_propagation()\n",
"sd.show(details=True)\n"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|12___6___ ______7__ 12___6_8_|___4___89 12_4_____ _2_____8_|\n",
"|_____6___ 1__4__7__ 12_4__7__|123______ ________9 ____5____|__34___8_ 12_4_____ _2____78_|\n",
"|12_______ ________9 _______8_|123______ ___4_____ 12_______|__3_5____ _____6___ _2____7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 1___5____ 1___5___9|1_____7_9 _____6___ 1__4__7_9|___45____ _2_45____ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|1_______9 _2_______ 1__4____9|___45__8_ ___45____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 ______78_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|_23______ ___45____ _2345____|_2__56___ _______8_ _2___6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd.show(details=True)\n",
"# Let's save this Sudoku for later.\n",
"sd_partially_solved = Sudoku(sd)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What can we do when constraint propagation fails? The only thing we can do is make a guess. We can take one of the cells whose set contains multiple digits, such as cell (2, 0) (starting counting at 0, as in Python), which contains {1,2} , and try one of the values, for instance 1 .\n",
"We can see whether assigning to the cell the singleton set {1} leads to the solution. If not, we try the value {2} instead. If the Sudoku problem has a solution, one of these two values must work.\n",
"\n",
"Classically, this way of searching for a solution has been called search with backtracking. The backtracking is because we can choose a value, say 1 , and then do a lot of work, propagating the new constraint, making further guesses, and so on and so forth. If that does not pan out, we must \"backtrack\" and return to our guess, choosing (in our example) 2 instead.\n",
"\n",
"Let us implement search with backtracking. What we need to do is something like this:\n",
"\n",
"search():\n",
"\n",
"- propagate constraints.\n",
"- if solved, hoorrayy!\n",
"- if impossible, raise Unsolvable()\n",
"- if not fully solved, pick a cell with multiple digits possible, and iteratively:\n",
" - Assign one of the possible values to the cell.\n",
" - Call search() with that value for the cell.\n",
" - If Unsolvable is raised by the search() call, move on to the next value.\n",
" - If all values returned Unsolvable (if we tried them all), then we raise Unsolvable.\n",
"\n",
"So we see that search() is a recursive function.\n",
"From the pseudo-code above, we see that it might be better to pick a cell with few values possible at step 4 above, so as to make our chances of success as good as possible. For instance, it is much better to choose a cell with set {1,2} than one with set {1,3,5,6,7,9} , as the probability of success is 1/2 in the first case and 1/6 in the second case. Of course, it may be possible to come up with much better heuristics to guide our search, but this will have to do so far.\n",
"\n",
"One fine point with the search above is the following. So far, an object has a self.m matrix, which contains the status of the Sudoku solution. We cannot simply pass self.m recursively to search(), because in the course of the search and constraint propagation, self.m will be modified, and there is no easy way to keep track of these modifications. Rather, we will write search() as a method, and when we call it, we will:\n",
"\n",
"- First, create a copy of the current object via the Sudoku constructor, so we have a copy we can modify.\n",
"- Second, we assign one of the values to the cell, as above;\n",
"- Third, we will call the search() method of that object.\n",
"\n",
"Furthermore, when a solution is found, as in the hoorraay! above, we need to somehow return the solution. There are two ways of doing this: via standard returns, or by raising an exception."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_done(self):\n",
" for i in range(9):\n",
" for j in range(9):\n",
" if len(self.m[i][j]) > 1:\n",
" return False\n",
" return True\n",
"\n",
"Sudoku.done = sudoku_done\n",
"\n",
"\n",
"def sudoku_search(self, new_cell=None):\n",
" to_propagate = None if new_cell is None else {new_cell}\n",
" self.full_propagation(to_propagate=to_propagate)\n",
" if self.done():\n",
" return self # We are a solution\n",
" # We need to search. Picks a cell with as few candidates as possible.\n",
" candidates = [(len(self.m[i][j]), i, j)\n",
" for i in range(9) for j in range(9) if len(self.m[i][j]) > 1]\n",
" _, i, j = min(candidates)\n",
" values = self.m[i][j]\n",
" # values contains the list of values we need to try for cell i, j.\n",
" # print(\"Searching values\", values, \"for cell\", i, j)\n",
" for x in values:\n",
" # print(\"Trying value\", x)\n",
" sd = Sudoku(self)\n",
" sd.m[i][j] = {x}\n",
" try:\n",
" # If we find a solution, we return it.\n",
" return sd.search(new_cell=(i, j))\n",
" except Unsolvable:\n",
" # Go to next value.\n",
" pass\n",
" # All values have been tried, apparently with no success.\n",
" raise Unsolvable()\n",
"\n",
"Sudoku.search = sudoku_search\n",
"\n",
"\n",
"def sudoku_solve(self, do_print=True):\n",
" try:\n",
" r = self.search()\n",
" if do_print:\n",
" print(\"We found a solution:\")\n",
" r.show()\n",
" return r\n",
" except Unsolvable:\n",
" if do_print:\n",
" print(\"The problem has no solutions\")\n",
"\n",
"Sudoku.solve = sudoku_solve\n"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"We found a solution:\n",
"+---+---+---+\n",
"|531|678|942|\n",
"|674|295|318|\n",
"|298|341|567|\n",
"+---+---+---+\n",
"|859|167|423|\n",
"|426|853|791|\n",
"|713|924|856|\n",
"+---+---+---+\n",
"|165|739|284|\n",
"|987|412|635|\n",
"|342|586|179|\n",
"+---+---+---+\n"
]
},
{
"data": {
"text/plain": [
"<__main__.Sudoku at 0x629a6d0>"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6___95___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___41___5',\n",
" '____8__79'\n",
"])\n",
"sd.solve()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The choice - constraint propagation - recursion paradigm.\n",
"\n",
"We have learned a general strategy for solving difficult problems. The strategy can be summarized thus: choice - constraint propagation - recursion.\n",
"\n",
"In the choice step, we make one guess from a set of possible guesses. If we want our search for a solution to be exhaustive, as in the above Sudoku example, we ensure that we try iteratively all choices from a set of choices chosen so that at least one of them must succeed. In the above example, we know that at least one of the digit values must be the true one, hence our search is exhaustive. In other cases, we can trade off exhaustiveness for efficiency, and we may try only a few choices, guided perhaps by an heuristic.\n",
"\n",
"The constraint propagation step propagates the consequences of the choice to the problem. Each choice thus gives rise to a new problem, which is a little bit simpler than the original one as some of the possible choices, that is, some of its complexity, has been removed. In the Sudoku case, the new problem has less indetermination, as at least one more of its cells has a known digit in it.\n",
"\n",
"The problems resulting from constraint propagation, while simpler, may not be solved yet. Hence, we recur, calling the solution procedure on them as well. As these problems are simpler (they contain fewer choices), eventually the recursion must reach a point where no more choice is possible, and whether constraint propagation should yield a completely defined problem, one of which it is possible to say whether it is solvable or not with a trivial test. This forms the base case for the recursion.\n",
"\n",
"This solution strategy applies very generally, to problems well beyond Sudoku."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Part 2: Digits must go somewhere\n",
"\n",
"If you have played Sudoku before, you might have found the way we solved Sudoku puzzles a bit odd. The constraint we encoded is:\n",
"\n",
" - If a digit appears in a cell, it cannot appear anywhere else on the same row, column, or 3x3 block as the cell.\n",
"\n",
"This is a rule of Sudoku. Normally, however, we hear Sudoku described in a different way:\n",
"\n",
" - Every column, row, and 3x3 block should contain all the 1...9 digits exactly once.\n",
"\n",
"There are two questions. The first is: are the two definitions equivalent? Well, no; the first definition does not say what the digits are (e.g., does not rule out 0). But in our Sudoku representation, we start by saying that every cell can contain only one of 1...9. If every row (or column, or 3x3 block) cannot contain more than one repetition of each digit, and if there are 9 digits and 9 cells in the row (or column, or block), then clearly every digit must appear exactly once in the row (or column, or block). So once the set of digits is specified, the two definitions are equivalent.\n",
"\n",
"The second question is: but still, what happens to the method we usually employ to solve Sudoku? I generally don't solve Sudoku puzzles by focusing on one cell at a time, and thinking: is it the case that this call can contain only one digit? This is the strategy employed by the solver above. But it is not the strategy I normally use. I generally solve Sudoku puzzles by looking at a block (or row, or column), and thinking: let's consider the digit ? ( 1≤?≤9 ). Where can it go in the block? And if I find that the digit can go in one block cell only, I write it there.\n",
"Does the solver work even without this \"where can it go\" strategy? And can we make it follow it?\n",
"\n",
"The solver works even without the \"where can it go\" strategy because it exaustively tries all possibilities. This means the solver works without the strategy; it does not say that the solver works well without the strategy.\n",
"\n",
"We can certainly implement the where can it go strategy, as part of constraint propagation; it would make our solver more efficient."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Question 3: A better full_propagation method\n",
"\n",
"### Not a real question; just copy some previous code into a new method.\n",
"\n",
"There is a subtle point in applying the where can it go heuristics.\n",
"\n",
"Before, when our only constraint was the uniqueness in each row, column, and block, we needed to propagate only from cells that hold a singleton value. If a cell held a non-singleton set of digits, such as {2,5} , no values could be ruled out as a consequence of this on the same row, column, or block.\n",
"\n",
"The where can it go heuristic, instead, benefits from knowing that in a cell, the set of values went for instance from {2,3,5} to {2,5} : by ruling out the possibility of a 3 in this cell, it may be possibe to deduct that the digit 3 can appear in only one (other) place in the block, and place it there.\n",
"\n",
"Thus, we modify the full_propagation method. The method does:\n",
"\n",
"- Repeat:\n",
" - first does propagation as before, based on singletons;\n",
" - then, it applies the where can it go heuristic on the whole Sudoku board.\n",
"- until there is nothing more that can be propagated.\n",
"\n",
"Thus, we replace the full_propagation method previously defined with this new one, where the (A) block of code is what you previously wrote in full_propagation. You don't need to write new code here: just copy your solution for full_propagation into the (A) block below."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_full_propagation_with_where_can_it_go(self, to_propagate=None):\n",
" if to_propagate is None:\n",
" to_propagate = {(i, j) for i in range(9) for j in range(9)}\n",
" while len(to_propagate) > 0:\n",
" # Here is your previous solution code from (A) in full_propagation.\n",
" # Please copy it below.\n",
" # YOUR CODE HERE\n",
" if to_propagate is None:\n",
" to_propagate = {(i, j) for i in range(9) for j in range(9)}\n",
"\n",
" # Now we check whether there is any other propagation that we can\n",
" # get from the where can it go rule.\n",
" to_propagate = self.where_can_it_go()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Question 4: Implement the occurs_once_in_sets helper\n",
"\n",
"To implement the where_can_it_go method, let us write a helper function, or better, let's have you write it. Given a sequence of sets ?1,?2,…,??, we want to obtain the set of elements that appear in exactly one of the sets (that is, they appear in one set, and only in one set). Mathematically, we can write this as\n",
"\n",
"(?1∖(?2∪⋯∪??))∪(?2∖(?1∪?3∪⋯∪??))∪⋯∪(??∖(?1∪⋯∪??−1))\n",
"\n",
"even though that's certainly not the easiest way to compute it! The problem can be solved with the help of defaultdict to count the occurrences, and is 5 lines long. Of course, other solutions are possible as well."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"from collections import defaultdict\n",
"from nose.tools import assert_equal\n",
"def occurs_once_in_sets(set_sequence):\n",
" assert_equal(occurs_once_in_sets([{1, 2}, {2, 3}]), {1, 3})\n",
" assert_equal(occurs_once_in_sets([]), set())\n",
" assert_equal(occurs_once_in_sets([{2, 3, 4}]), {2, 3, 4})\n",
" assert_equal(occurs_once_in_sets([set()]), set())\n",
" assert_equal(occurs_once_in_sets([{2, 3, 4, 5, 6}, {5, 6, 7, 8}, {5, 6, 7}, {4, 6, 7}]), {2, 3, 8})"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"We found a solution:\n",
"+---+---+---+\n",
"|531|678|942|\n",
"|674|295|318|\n",
"|298|341|567|\n",
"+---+---+---+\n",
"|859|167|423|\n",
"|426|853|791|\n",
"|713|924|856|\n",
"+---+---+---+\n",
"|165|739|284|\n",
"|987|412|635|\n",
"|342|586|179|\n",
"+---+---+---+\n"
]
},
{
"data": {
"text/plain": [
"<__main__.Sudoku at 0x629af30>"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6___95___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___41___5',\n",
" '____8__79'\n",
"])\n",
"sd.solve()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Question 5: Implement where can it go.\n",
"\n",
"We are now ready to write -- or better, to have you write -- the where can it go method.\n",
"The method is global: it examines all rows, all columns, and all blocks.\n",
"If it finds that in a row (or column, or block), a value can fit in only one cell, and that cell is not currently a singleton (for otherwise there is nothing to be done), it sets the value in the cell, and it adds the cell to the newly_singleton set that is returned, just as in propagate_cell. The portion of method that you need to write is about two dozen lines of code long."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"def sudoku_where_can_it_go(self):\n",
" newly_singleton = set()\n",
" return newly_singleton\n",
"Sudoku.where_can_it_go = sudoku_where_can_it_go\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Original:\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|12___6___ ______7__ 12___6_8_|___4___89 12_4_____ _2_____8_|\n",
"|_____6___ 1__4__7__ 12_4__7__|123______ ________9 ____5____|__34___8_ 12_4_____ _2____78_|\n",
"|12_______ ________9 _______8_|123______ ___4_____ 12_______|__3_5____ _____6___ _2____7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 1___5____ 1___5___9|1_____7_9 _____6___ 1__4__7_9|___45____ _2_45____ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|1_______9 _2_______ 1__4____9|___45__8_ ___45____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 ______78_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|_23______ ___45____ _2345____|_2__56___ _______8_ _2___6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
},
{
"ename": "AssertionError",
"evalue": "Items in the second set but not the first:\n(3, 2)\n(2, 6)\n(7, 1)\n(5, 6)\n(2, 8)\n(8, 0)\n(1, 6)\n(0, 5)\n(2, 3)\n(5, 1)\n(3, 7)\n(0, 3)\n(8, 5)\n(0, 8)\n(5, 3)\n(5, 5)\n(8, 1)\n(5, 7)\n(3, 1)\n(0, 6)\n(1, 8)\n(3, 6)\n(5, 2)\n(1, 1)",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAssertionError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 11\u001b[0m {(3, 2), (2, 6), (7, 1), (5, 6), (2, 8), (8, 0), (0, 5), (1, 6),\n\u001b[0;32m 12\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m3\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m7\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m8\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m8\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m (8, 1), (5, 7), (3, 1), (0, 6), (1, 8), (3, 6), (5, 2), (1, 1)})\n\u001b[0m\u001b[0;32m 14\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"After where can it go:\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 15\u001b[0m \u001b[0msd\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mshow\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdetails\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32m~\\Anaconda3\\lib\\unittest\\case.py\u001b[0m in \u001b[0;36massertEqual\u001b[1;34m(self, first, second, msg)\u001b[0m\n\u001b[0;32m 837\u001b[0m \"\"\"\n\u001b[0;32m 838\u001b[0m \u001b[0massertion_func\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_getAssertEqualityFunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfirst\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msecond\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 839\u001b[1;33m \u001b[0massertion_func\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfirst\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msecond\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mmsg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 840\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 841\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0massertNotEqual\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfirst\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msecond\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32m~\\Anaconda3\\lib\\unittest\\case.py\u001b[0m in \u001b[0;36massertSetEqual\u001b[1;34m(self, set1, set2, msg)\u001b[0m\n\u001b[0;32m 1097\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1098\u001b[0m \u001b[0mstandardMsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;34m'\\n'\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mlines\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1099\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfail\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_formatMessage\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mstandardMsg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1100\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1101\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0massertIn\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmember\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcontainer\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;32m~\\Anaconda3\\lib\\unittest\\case.py\u001b[0m in \u001b[0;36mfail\u001b[1;34m(self, msg)\u001b[0m\n\u001b[0;32m 678\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mfail\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 679\u001b[0m \u001b[1;34m\"\"\"Fail immediately, with the given message.\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 680\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfailureException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 681\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 682\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0massertFalse\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mexpr\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mAssertionError\u001b[0m: Items in the second set but not the first:\n(3, 2)\n(2, 6)\n(7, 1)\n(5, 6)\n(2, 8)\n(8, 0)\n(1, 6)\n(0, 5)\n(2, 3)\n(5, 1)\n(3, 7)\n(0, 3)\n(8, 5)\n(0, 8)\n(5, 3)\n(5, 5)\n(8, 1)\n(5, 7)\n(3, 1)\n(0, 6)\n(1, 8)\n(3, 6)\n(5, 2)\n(1, 1)"
]
}
],
"source": [
"sd = Sudoku.from_string('[[[5], [3], [1, 2, 4], [1, 2, 6], [7], [1, 2, 6, 8], [4, 8, 9], [1, 2, 4], [2, 8]], [[6], [1, 4, 7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3, 4, 8], [1, 2, 4], [2, 7, 8]], [[1, 2], [9], [8], [1, 2, 3], [4], [1, 2], [3, 5], [6], [2, 7]], [[8], [1, 5], [1, 5, 9], [1, 7, 9], [6], [1, 4, 7, 9], [4, 5], [2, 4, 5], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1, 5], [1, 3, 5, 9], [1, 9], [2], [1, 4, 9], [4, 5, 8], [4, 5], [6]], [[1, 9], [6], [1, 5, 7, 9], [5, 7, 9], [3], [7, 9], [2], [8], [4]], [[2, 9], [7, 8], [2, 7, 9], [4], [1], [2, 7, 9], [6], [3], [5]], [[2, 3], [4, 5], [2, 3, 4, 5], [2, 5, 6], [8], [2, 6], [1], [7], [9]]]')\n",
"print(\"Original:\")\n",
"sd.show(details=True)\n",
"new_singletons = set()\n",
"while True:\n",
" new_s = sd.where_can_it_go()\n",
" if len(new_s) == 0:\n",
" break\n",
" new_singletons |= new_s\n",
"assert_equal(new_singletons,\n",
" {(3, 2), (2, 6), (7, 1), (5, 6), (2, 8), (8, 0), (0, 5), (1, 6),\n",
" (2, 3), (3, 7), (0, 3), (5, 1), (0, 8), (8, 5), (5, 3), (5, 5),\n",
" (8, 1), (5, 7), (3, 1), (0, 6), (1, 8), (3, 6), (5, 2), (1, 1)})\n",
"print(\"After where can it go:\")\n",
"sd.show(details=True)\n",
"sdd = Sudoku.from_string('[[[5], [3], [1, 2, 4], [6], [7], [8], [9], [1, 2, 4], [2]], [[6], [7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3], [1, 2, 4], [8]], [[1, 2], [9], [8], [3], [4], [1, 2], [5], [6], [7]], [[8], [5], [9], [1, 9, 7], [6], [1, 4, 9, 7], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[1, 9], [6], [1, 5, 9, 7], [9, 5, 7], [3], [9, 7], [2], [8], [4]], [[9, 2], [8], [9, 2, 7], [4], [1], [9, 2, 7], [6], [3], [5]], [[3], [4], [2, 3, 4, 5], [2, 5, 6], [8], [6], [1], [7], [9]]]')\n",
"print(\"The above should be equal to:\")\n",
"sdd.show(details=True)\n",
"assert_equal(sd, sdd)\n",
"\n",
"sd = Sudoku([\n",
" '___26_7_1',\n",
" '68__7____',\n",
" '1____45__',\n",
" '82_1___4_',\n",
" '__46_2___',\n",
" '_5___3_28',\n",
" '___3___74',\n",
" '_4__5__36',\n",
" '7_3_18___'\n",
"])\n",
"print(\"Another Original:\")\n",
"sd.show(details=True)\n",
"print(\"Propagate once:\")\n",
"sd.propagate_all_cells_once()\n",
"# sd.show(details=True)\n",
"new_singletons = set()\n",
"while True:\n",
" new_s = sd.where_can_it_go()\n",
" if len(new_s) == 0:\n",
" break\n",
" new_singletons |= new_s\n",
"print(\"After where can it go:\")\n",
"sd.show(details=True)\n",
"sdd = Sudoku.from_string('[[[4], [3], [5], [2], [6], [9], [7], [8], [1]], [[6], [8], [2], [5], [7], [1], [4], [9], [3]], [[1], [9], [7], [8], [3], [4], [5], [6], [2]], [[8], [2], [6], [1], [9], [5], [3], [4], [7]], [[3], [7], [4], [6], [8], [2], [9], [1], [5]], [[9], [5], [1], [7], [4], [3], [6], [2], [8]], [[5], [1], [1, 2, 5, 6, 8, 9], [3], [2], [6], [1, 2, 8, 9], [7], [4]], [[2], [4], [1, 2, 8, 9], [9], [5], [7], [1, 2, 8, 9], [3], [6]], [[7], [6], [3], [4], [1], [8], [2], [5], [9]]]')\n",
"print(\"The above should be equal to:\")\n",
"sdd.show(details=True)\n",
"assert_equal(sd, sdd)\n"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Newly singleton: set()\n",
"Resulting Sudoku:\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|12___6___ ______7__ 12___6_8_|___4___89 12_4_____ _2_____8_|\n",
"|_____6___ 1__4__7__ 12_4__7__|123______ ________9 ____5____|__34___8_ 12_4_____ _2____78_|\n",
"|12_______ ________9 _______8_|123______ ___4_____ 12_______|__3_5____ _____6___ _2____7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 1___5____ 1___5___9|1_____7_9 _____6___ 1__4__7_9|___45____ _2_45____ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|1_______9 _2_______ 1__4____9|___45__8_ ___45____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 ______78_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|_23______ ___45____ _2345____|_2__56___ _______8_ _2___6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd = Sudoku(sd_partially_solved)\n",
"newly_singleton = sd.where_can_it_go()\n",
"print(\"Newly singleton:\", newly_singleton)\n",
"print(\"Resulting Sudoku:\")\n",
"sd.show(details=True)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"Sudoku.full_propagation = sudoku_full_propagation_with_where_can_it_go"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Initial:\n",
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|.95|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|41.|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"After full propagation with where can it go:\n",
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|.95|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|41.|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6___95___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___41___5',\n",
" '____8__79'\n",
"])\n",
"print(\"Initial:\")\n",
"sd.show()\n",
"sd.full_propagation()\n",
"print(\"After full propagation with where can it go:\")\n",
"sd.show()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"No! We still cannot! But if we compare the above with the previous attempt, we see that the heuristic led to much more progress; very few positions still remain to be determined via search."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Question 6: Solving some problems from example sites\n",
"\n",
"Let us see how long it takes us to solve examples found around the Web. We consider a few from this site. You should be able to complete all of these tests in a short amount of time."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"We found a solution:\n",
"+---+---+---+\n",
"|121|618|111|\n",
"|581|119|711|\n",
"|111|141|111|\n",
"+---+---+---+\n",
"|371|111|511|\n",
"|611|111|114|\n",
"|118|111|113|\n",
"+---+---+---+\n",
"|111|121|111|\n",
"|119|811|136|\n",
"|111|316|191|\n",
"+---+---+---+\n",
"Solved in 0.025991439819335938 seconds\n"
]
}
],
"source": [
"import time\n",
"sd = Sudoku([\n",
" '_2_6_8___',\n",
" '58___97__',\n",
" '____4____',\n",
" '37____5__',\n",
" '6_______4',\n",
" '__8____13',\n",
" '____2____',\n",
" '__98___36',\n",
" '___3_6_9_'\n",
"])\n",
"t = time.time()\n",
"sd.solve()\n",
"elapsed = time.time() - t\n",
"print(\"Solved in\", elapsed, \"seconds\")\n",
"\n",
"assert elapsed < 5"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"We found a solution:\n",
"+---+---+---+\n",
"|111|611|411|\n",
"|711|113|611|\n",
"|111|191|181|\n",
"+---+---+---+\n",
"|111|111|111|\n",
"|151|181|113|\n",
"|111|316|145|\n",
"+---+---+---+\n",
"|141|211|161|\n",
"|913|111|111|\n",
"|121|111|111|\n",
"+---+---+---+\n",
"Solved in 0.030971527099609375 seconds\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '___6__4__',\n",
" '7____36__',\n",
" '____91_8_',\n",
" '_________',\n",
" '_5_18___3',\n",
" '___3_6_45',\n",
" '_4_2___6_',\n",
" '9_3______',\n",
" '_2____1__'\n",
"])\n",
"t = time.time()\n",
"sd.solve()\n",
"elapsed = time.time() - t\n",
"print(\"Solved in\", elapsed, \"seconds\")\n",
"assert elapsed < 5"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"We found a solution:\n",
"+---+---+---+\n",
"|611|118|941|\n",
"|911|116|111|\n",
"|171|141|111|\n",
"+---+---+---+\n",
"|211|611|111|\n",
"|111|111|211|\n",
"|189|112|111|\n",
"+---+---+---+\n",
"|111|161|115|\n",
"|111|111|131|\n",
"|811|111|611|\n",
"+---+---+---+\n",
"Solved in 0.031981468200683594 seconds\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '6____894_',\n",
" '9____61__',\n",
" '_7__4____',\n",
" '2__61____',\n",
" '______2__',\n",
" '_89__2___',\n",
" '____6___5',\n",
" '_______3_',\n",
" '8____16__'\n",
"])\n",
"t = time.time()\n",
"sd.solve()\n",
"elapsed = time.time() - t\n",
"print(\"Solved in\", elapsed, \"seconds\")\n",
"assert elapsed < 10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Trying puzzles in bulk\n",
"\n",
"Let us try the puzzles found at https://raw.githubusercontent.com/shadaj/sudoku/master/sudoku17.txt; apparently lines 517 and 6361 are very hard)."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"\n",
"r = requests.get(\"https://raw.githubusercontent.com/shadaj/sudoku/master/sudoku17.txt\")\n",
"puzzles = r.text.split()"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [],
"source": [
"def convert_to_our_format(s):\n",
" t = s.replace('0', '_')\n",
" r = []\n",
" for i in range(9):\n",
" r.append(t[i * 9: (i + 1) * 9])\n",
" return r"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"It took you 29.09717559814453 to solve the first 1000 Sudokus.\n"
]
}
],
"source": [
"t = 0\n",
"max_d = 0.\n",
"max_i = None\n",
"t = time.time()\n",
"for i, s in enumerate(puzzles[:1000]):\n",
" p = convert_to_our_format(puzzles[i])\n",
" sd = Sudoku(p)\n",
" sd.solve(do_print=False)\n",
"elapsed = time.time() - t\n",
"print(\"It took you\", elapsed, \"to solve the first 1000 Sudokus.\")\n",
"assert elapsed < 30"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
x/.ipynb_checkpoints/test-checkpoint.ipynb{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "YL840Zb3K3YN"
},
"source": [
"Before you turn this problem in, make sure everything runs as expected. First, **restart the kernel** (in the menubar, select Kernel$\\rightarrow$Restart) and then **run all cells** (in the menubar, select Cell$\\rightarrow$Run All).\n",
"\n",
"Make sure you fill in any place that says `YOUR CODE HERE` or \"YOUR ANSWER HERE\", as well as your name and collaborators below:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "kyvbHSwvK3YN"
},
"outputs": [],
"source": [
"NAME = \"\"\n",
"COLLABORATORS = \"\""
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "4EvX8SBVK3YO"
},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "K207tb0NK3YO",
"nbgrader": {
"checksum": "1bb3015d73460b9454dcf7bf22d8febc",
"grade": false,
"grade_id": "cell-52aae20f6abaae07",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"# Homework 12: Sudoku\n",
"\n",
"Copyright Luca de Alfaro, 2019-20. \n",
"License: [CC-BY-NC-ND](https://creativecommons.org/licenses/by-nc-nd/4.0/)."
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "BWYZGETUK3YO",
"nbgrader": {
"checksum": "52019f046baf0aebbacf0834ec20d544",
"grade": false,
"grade_id": "cell-e4a664f6b6259f37",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Submission\n",
"\n",
"[Please submit to this Google Form](https://docs.google.com/forms/d/e/1FAIpQLSf0r9gB2y6Wcf4pWM9aqpzyZfQOvNqFJol36NtS4Msdvd3VXA/viewform?usp=sf_link).\n",
"\n",
"Deadline: Tuesday December 8, 11pm (check on Canvas for updated information)."
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "qQc37X_2K3YO",
"nbgrader": {
"checksum": "242c69b30ed9c8bc2f876409c18fdee4",
"grade": false,
"grade_id": "cell-900f0605b35de62e",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Test Format\n",
"\n",
"There are 6 questions, but not all of them require you to write code: \n",
"* Question 1 asks you to complete the code for propagating a single cell.\n",
"* Question 2 asks you to write the code to propagate all cells. \n",
"* For Question 3, you just need to copy your solution to Question 2 in another place. \n",
"* Question 4 asks you to implement a helper function that detects elements that occur in only one of a list of sets. \n",
"* Question 5 asks you to implement the _where can it go_ heuristic. \n",
"* Question 6 simply checks that your code is efficient; you don't need to write any code. \n",
"\n",
"There are a total of 70 points. "
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "i3cY6FkMK3YO",
"nbgrader": {
"checksum": "c27cbed28b99b8aa403972904798d81c",
"grade": false,
"grade_id": "cell-4bba7c934801db1",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"Let us write a [Sudoku](https://en.wikipedia.org/wiki/Sudoku) solver. We want to get as input a Sudoku with some cells filled with values, and we want to get as output a solution, if one exists, and otherwise a notice that the input Sudoku puzzle has no solutions. \n",
"\n",
"You will wonder, why spend so much time on Sudoku? \n",
"\n",
"For two reasons. \n",
"\n",
"First, the way we go about solving Sudoku is prototypical of a very large number of problems in computer science. In these problems, the solution is attained through a mix of search (we attempt to fill a square with a number and see if it works out), and constraint propagation (if we fill a square with, say, a 1, then there can be no 1's in the same row, column, and 3x3 square).\n",
"\n",
"Second, and related, the way we go about solving Sudoku puzzles is closely related to how [SAT solvers](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem#Algorithms_for_solving_SAT) work. So closely related, in fact, that while _we_ describe for you how a Sudoku solver works, _you_ will have to write a SAT solver as exercise. \n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "BkNrCHlJK3YO",
"nbgrader": {
"checksum": "496163a260419b914fa200f192914121",
"grade": false,
"grade_id": "cell-c285b4ebc76bbc97",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Sudoku representation\n",
"\n",
"First, let us do some grunt work and define a representation for a Sudoku problem. \n",
"\n",
"One initial idea would be to represent a Sudoku problem via a $9 \\times 9$ matrix, where each entry can be either a digit from 1 to 9, or 0 to signify \"blank\". This would work in some sense, but it would not be a very useful representation. If you have solved Sudoku by hand (and if you have not, please go and solve a couple; it will teach you a lot about what we need to do), you will know that the following strategy works: \n",
"\n",
"Repeat: \n",
"* Look at all blank spaces. Can you find one where only one digit fits? If so, write the digit there. \n",
"* If you cannot find any blank space as above, try to find one where only a couple or so digits can fit. Try putting in one of those digits, and see if you can solve the puzzle with that choice. If not, backtrack, and try another digit. \n",
"\n",
"Thus, it will be very useful to us to remember not only the known digits, but also, which digits can fit into any blank space. \n",
"Hence, we represent a Sudoku problem via a $9 \\times 9$ matrix of _sets_: each set contains the digits that can fit in a given space. \n",
"Of course, a known digit is just a set containing only one element. \n",
"We will solve a Sudoku problem by progressively \"shrinking\" these sets of possibilities, until they all contain exactly one element. \n",
"\n",
"Let us write some code that enables us to define a Sudoku problem, and display it for us; this will be very useful both for our fun and for debugging. \n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "psqNkEIwK3YO",
"nbgrader": {
"checksum": "9e5d7d22fdc7a5a7a07a27c11a52790e",
"grade": false,
"grade_id": "cell-151003cf8fc7620a",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"First, though, let's write a tiny helper function that returns the only element from a singleton set."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"deletable": false,
"editable": false,
"id": "mU1MzrakK3YO",
"nbgrader": {
"checksum": "c50389c94bd5aa15b8730458d324e27a",
"grade": false,
"grade_id": "cell-827ad7be0aa4e5c4",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"outputs": [],
"source": [
"def getel(s):\n",
" \"\"\"Returns the unique element in a singleton set (or list).\"\"\"\n",
" assert len(s) == 1\n",
" return list(s)[0]\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"deletable": false,
"editable": false,
"id": "4uOI-cGeK3YO",
"nbgrader": {
"checksum": "55b54074dab77e5220faf45a5cf60dba",
"grade": false,
"grade_id": "cell-2263b8e9a5ec13ce",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"outputs": [],
"source": [
"import json\n",
"\n",
"class Sudoku(object):\n",
"\n",
" def __init__(self, elements):\n",
" \"\"\"Elements can be one of:\n",
" Case 1: a list of 9 strings of length 9 each.\n",
" Each string represents a row of the initial Sudoku puzzle,\n",
" with either a digit 1..9 in it, or with a blank or _ to signify\n",
" a blank cell.\n",
" Case 2: an instance of Sudoku. In that case, we initialize an\n",
" object to be equal (a copy) of the one in elements.\n",
" Case 3: a list of list of sets, used to initialize the problem.\"\"\"\n",
" if isinstance(elements, Sudoku):\n",
" # We let self.m consist of copies of each set in elements.m\n",
" self.m = [[x.copy() for x in row] for row in elements.m]\n",
" else:\n",
" assert len(elements) == 9\n",
" for s in elements:\n",
" assert len(s) == 9\n",
" # We let self.m be our Sudoku problem, a 9x9 matrix of sets.\n",
" self.m = []\n",
" for s in elements:\n",
" row = []\n",
" for c in s:\n",
" if isinstance(c, str):\n",
" if c.isdigit():\n",
" row.append({int(c)})\n",
" else:\n",
" row.append({1, 2, 3, 4, 5, 6, 7, 8, 9})\n",
" else:\n",
" assert isinstance(c, set)\n",
" row.append(c)\n",
" self.m.append(row)\n",
"\n",
"\n",
" def show(self, details=False):\n",
" \"\"\"Prints out the Sudoku matrix. If details=False, we print out\n",
" the digits only for cells that have singleton sets (where only\n",
" one digit can fit). If details=True, for each cell, we display the\n",
" sets associated with the cell.\"\"\"\n",
" if details:\n",
" print(\"+-----------------------------+-----------------------------+-----------------------------+\")\n",
" for i in range(9):\n",
" r = '|'\n",
" for j in range(9):\n",
" # We represent the set {2, 3, 5} via _23_5____\n",
" s = ''\n",
" for k in range(1, 10):\n",
" s += str(k) if k in self.m[i][j] else '_'\n",
" r += s\n",
" r += '|' if (j + 1) % 3 == 0 else ' '\n",
" print(r)\n",
" if (i + 1) % 3 == 0:\n",
" print(\"+-----------------------------+-----------------------------+-----------------------------+\")\n",
" else:\n",
" print(\"+---+---+---+\")\n",
" for i in range(9):\n",
" r = '|'\n",
" for j in range(9):\n",
" if len(self.m[i][j]) == 1:\n",
" r += str(getel(self.m[i][j]))\n",
" else:\n",
" r += \".\"\n",
" if (j + 1) % 3 == 0:\n",
" r += \"|\"\n",
" print(r)\n",
" if (i + 1) % 3 == 0:\n",
" print(\"+---+---+---+\")\n",
"\n",
"\n",
" def to_string(self):\n",
" \"\"\"This method is useful for producing a representation that\n",
" can be used in testing.\"\"\"\n",
" as_lists = [[list(self.m[i][j]) for j in range(9)] for i in range(9)]\n",
" return json.dumps(as_lists)\n",
"\n",
"\n",
" @staticmethod\n",
" def from_string(s):\n",
" \"\"\"Inverse of above.\"\"\"\n",
" as_lists = json.loads(s)\n",
" as_sets = [[set(el) for el in row] for row in as_lists]\n",
" return Sudoku(as_sets)\n",
"\n",
"\n",
" def __eq__(self, other):\n",
" \"\"\"Useful for testing.\"\"\"\n",
" return self.m == other.m\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "O9WncZk0K3YO",
"nbgrader": {
"checksum": "b82ad66ac4c34a8cb00a34e3b29b535a",
"grade": false,
"grade_id": "cell-cb1a30c2763d5d2",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"Let us input a problem (the Sudoku example found on [this Wikipedia page](https://en.wikipedia.org/wiki/Sudoku)) and check that our serialization and deserialization works."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"deletable": false,
"editable": false,
"id": "o0k8BWIYK3YO",
"nbgrader": {
"checksum": "d019c9b4fcd041d57ff1e843e26713d6",
"grade": false,
"grade_id": "cell-1f083f349fe0572b",
"locked": true,
"schema_version": 1,
"solution": false
},
"outputId": "75e17fea-743d-417e-edbc-2c1499e59b34"
},
"outputs": [],
"source": [
"# Let us ensure that nose is installed.\n",
"try:\n",
" from nose.tools import assert_equal, assert_true\n",
" from nose.tools import assert_false, assert_almost_equal\n",
"except:\n",
" !pip install nose\n",
" from nose.tools import assert_equal, assert_true\n",
" from nose.tools import assert_false, assert_almost_equal\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"deletable": false,
"editable": false,
"id": "mHTNoMeLK3YO",
"nbgrader": {
"checksum": "27b98c191045f976b457b3be6c1d6a96",
"grade": false,
"grade_id": "cell-abc219725ab90761",
"locked": true,
"schema_version": 1,
"solution": false
},
"outputId": "41424937-1e80-4ca1-ad5e-0f8d5bb89a6b"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|419|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 123456789|123456789 ______7__ 123456789|123456789 123456789 123456789|\n",
"|_____6___ 123456789 123456789|1________ ________9 ____5____|123456789 123456789 123456789|\n",
"|123456789 ________9 _______8_|123456789 123456789 123456789|123456789 _____6___ 123456789|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 123456789 123456789|123456789 _____6___ 123456789|123456789 123456789 __3______|\n",
"|___4_____ 123456789 123456789|_______8_ 123456789 __3______|123456789 123456789 1________|\n",
"|______7__ 123456789 123456789|123456789 _2_______ 123456789|123456789 123456789 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|123456789 _____6___ 123456789|123456789 123456789 123456789|_2_______ _______8_ 123456789|\n",
"|123456789 123456789 123456789|___4_____ 1________ ________9|123456789 123456789 ____5____|\n",
"|123456789 123456789 123456789|123456789 _______8_ 123456789|123456789 ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 123456789|123456789 ______7__ 123456789|123456789 123456789 123456789|\n",
"|_____6___ 123456789 123456789|1________ ________9 ____5____|123456789 123456789 123456789|\n",
"|123456789 ________9 _______8_|123456789 123456789 123456789|123456789 _____6___ 123456789|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 123456789 123456789|123456789 _____6___ 123456789|123456789 123456789 __3______|\n",
"|___4_____ 123456789 123456789|_______8_ 123456789 __3______|123456789 123456789 1________|\n",
"|______7__ 123456789 123456789|123456789 _2_______ 123456789|123456789 123456789 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|123456789 _____6___ 123456789|123456789 123456789 123456789|_2_______ _______8_ 123456789|\n",
"|123456789 123456789 123456789|___4_____ 1________ ________9|123456789 123456789 ____5____|\n",
"|123456789 123456789 123456789|123456789 _______8_ 123456789|123456789 ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"from nose.tools import assert_equal\n",
"\n",
"sd = Sudoku([\n",
" '53__7____',\n",
" '6__195___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___419__5',\n",
" '____8__79'\n",
"])\n",
"sd.show()\n",
"sd.show(details=True)\n",
"s = sd.to_string()\n",
"sdd = Sudoku.from_string(s)\n",
"sdd.show(details=True)\n",
"assert_equal(sd, sdd)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "h-9K2DgiK3YO",
"nbgrader": {
"checksum": "baa6de1989b3ab4080826bf41eeb615c",
"grade": false,
"grade_id": "cell-a7cb264a1f0dac69",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"Let's test our constructor statement when passed a Sudoku instance."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"deletable": false,
"editable": false,
"id": "adNYqw5RK3YO",
"nbgrader": {
"checksum": "7098be01aa745b1eef29a845fba0e40b",
"grade": false,
"grade_id": "cell-dfde422d5915c737",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"outputs": [],
"source": [
"sd1 = Sudoku(sd)\n",
"assert_equal(sd, sd1)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "hO48NsItK3YO",
"nbgrader": {
"checksum": "b3097e98b475306462250c74ee623b15",
"grade": false,
"grade_id": "cell-d7b56fa5387da0b5",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Constraint propagation\n",
"\n",
"When the set in a Sudoku cell contains only one element, this means that the digit at that cell is known. \n",
"We can then propagate the knowledge, ruling out that digit in the same row, in the same column, and in the same 3x3 cell. \n",
"\n",
"We first write a method that propagates the constraint from a single cell. The method will return the list of newly-determined cells, that is, the list of cells who also now (but not before) are associated with a 1-element set. This is useful, because we can then propagate the constraints from those cells in turn. Further, if an empty set is ever generated, we raise the exception Unsolvable: this means that there is no solution to the proposed Sudoku puzzle. \n",
"\n",
"We don't want to steal all the fun from you; thus, we will give you the main pieces of the implemenetation, but we ask you to fill in the blanks. We provide tests so you can catch any errors right away."
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "ohyrRNPmK3YO",
"nbgrader": {
"checksum": "c98b50917fa005145dc40c7f82d4fb5d",
"grade": false,
"grade_id": "cell-b0ec846f8a365d79",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Question 1: Propagating a single cell"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"deletable": false,
"editable": false,
"id": "DfzjK01SK3YO",
"nbgrader": {
"checksum": "7a884c4167806587e806224a15063749",
"grade": false,
"grade_id": "cell-fc9a968448c5cc79",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"outputs": [],
"source": [
"class Unsolvable(Exception):\n",
" pass\n",
"\n",
"\n",
"def sudoku_ruleout(self, i, j, x):\n",
" \"\"\"The input consists in a cell (i, j), and a value x.\n",
" The function removes x from the set self.m[i][j] at the cell, if present, and:\n",
" - if the result is empty, raises Unsolvable;\n",
" - if the cell used to be a non-singleton cell and is now a singleton\n",
" cell, then returns the set {(i, j)};\n",
" - otherwise, returns the empty set.\"\"\"\n",
" c = self.m[i][j]\n",
" n = len(c)\n",
" c.discard(x)\n",
" self.m[i][j] = c\n",
" if len(c) == 0:\n",
" raise Unsolvable()\n",
" return {(i, j)} if 1 == len(c) < n else set()\n",
"\n",
"Sudoku.ruleout = sudoku_ruleout\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "-FvPMye1K3YO",
"nbgrader": {
"checksum": "2140d3f1ccf2fcf45b6b99d653070802",
"grade": false,
"grade_id": "cell-644ba08a6176522a",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"The method `propagate_cell(ij)` takes as input a pair `ij` of coordinates. If the set of possible digits `self.m[i][j]` for cell i,j contains more than one digit, then no propagation is done. If the set contains a single digit `x`, then we: \n",
"\n",
"* Remove `x` from the sets of all other cells on the same row, column, and 3x3 block. \n",
"* Collect all the newly singleton cells that are formed, due to the digit `x` being removed, and we return them as a set. \n",
"\n",
"We give you an implementation that takes care of removing `x` from the same row, and we ask you to complete the implementation to take care of the column and 3x3 block as well."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"deletable": false,
"id": "jVngQvdSK3YO",
"nbgrader": {
"checksum": "238b2993e7002fe89ef7d9ea1e86388e",
"grade": false,
"grade_id": "cell-cb8846ecfc06e4a8",
"locked": false,
"schema_version": 1,
"solution": true
}
},
"outputs": [],
"source": [
"### Exercise: define cell propagation\n",
"\n",
"def sudoku_propagate_cell(self, ij):\n",
" \"\"\"Propagates the singleton value at cell (i, j), returning the list\n",
" of newly-singleton cells.\"\"\"\n",
" i, j = ij\n",
" if len(self.m[i][j]) > 1:\n",
" # Nothing to propagate from cell (i,j).\n",
" return set()\n",
" # We keep track of the newly-singleton cells.\n",
" newly_singleton = set()\n",
" x = getel(self.m[i][j]) # Value at (i, j).\n",
" # Same row.\n",
" for jj in range(9):\n",
" if jj != j: # Do not propagate to the element itself.\n",
" newly_singleton.update(self.ruleout(i, jj, x))\n",
" # Same column.\n",
" # YOUR CODE HERE\n",
" for ii in range(9):\n",
" if ii != i:\n",
" newly_singleton.update(self.ruleout(ii, j, x))\n",
" # Same block of 3x3 cells.\n",
" # YOUR CODE HERE\n",
" current_i, current_j = i // 3, j // 3\n",
" current_i, current_j = 3*current_i + 1, 3*current_j + 1\n",
" \n",
" for di in range(-1, 2):\n",
" for dj in range(-1, 2):\n",
" ni, nj = current_i + di, current_j + dj\n",
" if i != ni and j != nj:\n",
" newly_singleton.update(self.ruleout(ni, nj, x))\n",
" # Returns the list of newly-singleton cells.\n",
" return newly_singleton\n",
"\n",
"Sudoku.propagate_cell = sudoku_propagate_cell\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"deletable": false,
"id": "Yvv_sj14K3YO",
"nbgrader": {
"checksum": "6cd195cd2104c42b331eeaa641745991",
"grade": false,
"grade_id": "cell-aaf8944cfc81c6dd",
"locked": false,
"schema_version": 1,
"solution": true
}
},
"outputs": [],
"source": [
"# Here you can write your own tests if you like. \n",
"\n",
"# YOUR CODE HERE"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"deletable": false,
"editable": false,
"id": "ZZtJigfvK3YO",
"nbgrader": {
"checksum": "1d320009a9aff66baf1be3c34c9aa091",
"grade": true,
"grade_id": "cell-518cff90ecab53c2",
"locked": true,
"points": 10,
"schema_version": 1,
"solution": false
},
"outputId": "c1efe19f-a5a9-4717-da2a-04b3ccc0e2c5"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ _2_______|_____6___ ______7__ _______8_|________9 12_4_____ _2_______|\n",
"|_____6___ ______7__ 12_4__7__|123______ ________9 ____5____|__3______ 12_4_____ _______8_|\n",
"|12_______ ________9 _______8_|__3______ ___4_____ 12_______|____5____ _____6___ ______7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ ____5____ ________9|1_____7_9 _____6___ 1__4__7_9|___4_____ _2_______ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1________ __3______|________9 _2_______ ___4_____|_______8_ ____5____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 _______8_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|__3______ ___4_____ _2345____|_2__56___ _______8_ _____6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"Good! It was unsolvable.\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ _2_______|_____6___ ______7__ _______8_|________9 12_4_____ _23______|\n",
"|_____6___ ______7__ 12_4__7__|123______ ________9 ____5____|__3______ 12_4_____ _______8_|\n",
"|12_______ ________9 _______8_|__3______ ___4_____ 12_______|____5____ _____6___ ______7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ ____5____ ________9|1_____7_9 _____6___ 1__4__7_9|___4_____ _2_______ __3______|\n",
"|___4_____ _2_______ _____6___|_______8_ ____5____ __3______|______7__ ________9 1________|\n",
"|______7__ 1________ __3______|________9 _2_______ ___4_____|_______8_ ____5____ _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_______9 _____6___ 1___5_7_9|____5_7_9 __3______ ______7_9|_2_______ _______8_ ___4_____|\n",
"|_2______9 _______8_ _2____7_9|___4_____ 1________ _2____7_9|_____6___ __3______ ____5____|\n",
"|__3______ ___4_____ _2345____|_2__56___ _______8_ _____6___|1________ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"### 10 points: Tests for cell propagation\n",
"\n",
"tsd = Sudoku.from_string('[[[5], [3], [2], [6], [7], [8], [9], [1, 2, 4], [2]], [[6], [7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3], [1, 2, 4], [8]], [[1, 2], [9], [8], [3], [4], [1, 2], [5], [6], [7]], [[8], [5], [9], [1, 9, 7], [6], [1, 4, 9, 7], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[1, 9], [6], [1, 5, 9, 7], [9, 5, 7], [3], [9, 7], [2], [8], [4]], [[9, 2], [8], [9, 2, 7], [4], [1], [9, 2, 7], [6], [3], [5]], [[3], [4], [2, 3, 4, 5], [2, 5, 6], [8], [6], [1], [7], [9]]]')\n",
"tsd.show(details=True)\n",
"try:\n",
" tsd.propagate_cell((0, 2))\n",
"except Unsolvable:\n",
" print(\"Good! It was unsolvable.\")\n",
"else:\n",
" raise Exception(\"Hey, it was unsolvable\")\n",
"\n",
"tsd = Sudoku.from_string('[[[5], [3], [2], [6], [7], [8], [9], [1, 2, 4], [2, 3]], [[6], [7], [1, 2, 4, 7], [1, 2, 3], [9], [5], [3], [1, 2, 4], [8]], [[1, 2], [9], [8], [3], [4], [1, 2], [5], [6], [7]], [[8], [5], [9], [1, 9, 7], [6], [1, 4, 9, 7], [4], [2], [3]], [[4], [2], [6], [8], [5], [3], [7], [9], [1]], [[7], [1], [3], [9], [2], [4], [8], [5], [6]], [[1, 9], [6], [1, 5, 9, 7], [9, 5, 7], [3], [9, 7], [2], [8], [4]], [[9, 2], [8], [9, 2, 7], [4], [1], [9, 2, 7], [6], [3], [5]], [[3], [4], [2, 3, 4, 5], [2, 5, 6], [8], [6], [1], [7], [9]]]')\n",
"tsd.show(details=True)\n",
"assert_equal(tsd.propagate_cell((0, 2)), {(0, 8), (2, 0)})\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "la3lgaFdK3YO",
"nbgrader": {
"checksum": "e5ed2a3a7c5ee13c35949e28b4549ce4",
"grade": false,
"grade_id": "cell-6bdb89884ff9de73",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"### Propagating all cells, once\n",
"\n",
"The simplest thing we can do is propagate each cell, once. "
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"deletable": false,
"editable": false,
"id": "a7tylyeNK3YO",
"nbgrader": {
"checksum": "cf75628fec88e4a7eb1336f87733579d",
"grade": false,
"grade_id": "cell-f01c09cb45f93a07",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"outputs": [],
"source": [
"def sudoku_propagate_all_cells_once(self):\n",
" \"\"\"This function propagates the constraints from all singletons.\"\"\"\n",
" for i in range(9):\n",
" for j in range(9):\n",
" self.propagate_cell((i, j))\n",
"\n",
"Sudoku.propagate_all_cells_once = sudoku_propagate_all_cells_once\n"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"deletable": false,
"editable": false,
"id": "fO2u9xv6K3YO",
"nbgrader": {
"checksum": "ea85d958be7c22389daa6d8b5319089e",
"grade": false,
"grade_id": "cell-544f2797149c7f48",
"locked": true,
"schema_version": 1,
"solution": false
},
"outputId": "04c432b2-9b85-4284-e4f8-9ed6e686a681"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|8.3|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|...|28.|\n",
"|...|419|..5|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+---+---+---+\n",
"|53.|.7.|...|\n",
"|6..|195|...|\n",
"|.98|...|.6.|\n",
"+---+---+---+\n",
"|8..|.6.|..3|\n",
"|4..|853|..1|\n",
"|7..|.2.|..6|\n",
"+---+---+---+\n",
"|.6.|..7|284|\n",
"|...|419|.35|\n",
"|...|.8.|.79|\n",
"+---+---+---+\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|____5____ __3______ 12_4_____|_2___6___ ______7__ _2_4_6_8_|1__4___89 12_4____9 _2_4___8_|\n",
"|_____6___ _2_4__7__ _2_4__7__|1________ ________9 ____5____|__34__78_ _234_____ _2_4__78_|\n",
"|12_______ ________9 _______8_|_23______ __34_____ _2_4_____|1_345_7__ _____6___ _2_4__7__|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|_______8_ 12__5____ 12__5___9|____5_7_9 _____6___ 1__4__7__|___45_7_9 _2_45___9 __3______|\n",
"|___4_____ _2__5____ _2__56__9|_______8_ ____5____ __3______|____5_7_9 _2__5___9 1________|\n",
"|______7__ 1___5____ 1_3_5___9|____5___9 _2_______ 1__4_____|___45__89 ___45___9 _____6___|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n",
"|1_3_____9 _____6___ 1_345_7_9|__3_5_7__ __3_5____ ______7__|_2_______ _______8_ ___4_____|\n",
"|_23______ _2____78_ _23___7__|___4_____ 1________ ________9|__3__6___ __3______ ____5____|\n",
"|123______ 12_45____ 12345____|_23_56___ _______8_ _2___6___|1_34_6___ ______7__ ________9|\n",
"+-----------------------------+-----------------------------+-----------------------------+\n"
]
}
],
"source": [
"sd = Sudoku([\n",
" '53__7____',\n",
" '6__195___',\n",
" '_98____6_',\n",
" '8___6___3',\n",
" '4__8_3__1',\n",
" '7___2___6',\n",
" '_6____28_',\n",
" '___419__5',\n",
" '____8__79'\n",
"])\n",
"sd.show()\n",
"sd.propagate_all_cells_once()\n",
"sd.show()\n",
"sd.show(details=True)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"deletable": false,
"editable": false,
"id": "UrkX-YG_K3YO",
"nbgrader": {
"checksum": "c3998f1c5401444444d92ca72de4e24e",
"grade": false,
"grade_id": "cell-29a0e911623d5ef4",
"locked": true,
"schema_version": 1,
"solution": false
}
},
"source": [
"## Question 2: Propagating all cells, repeatedly\n",
"\n",
"This is a good beginning, but it's not quite enough. \n",
"As we propagate the constraints, cells that did not use to be singletons may have become singletons. For eample, in the above example, the center cell has become known to be a 5: we need to make sure that also these singletons are propagated. \n",
"\n",
"This is why we have written propagate_cell so that it returns the set of newly-singleton cells. \n",
"We need now to write a method `full_propagation` that at the beginning starts with a set of `to_propagate` cells (if it is not specified, then we just take it to consist of all singleton cells). Then, it picks a cell from the to_propagate set, and propagates from it, adding any newly singleton cell to to_propagate. Once there are no more cells to be propagated, the method returns. \n",
"If this sounds similar to graph reachability, it is ... because it is! It is once again the algorithmic pattern of keeping a list of work to be done, then iteratively picking an element from the list, doing it, possibly updating the list of work to be done with new work that has to be done as a result of what we just did, and continuing in this fashion until there is nothing left to do. \n",
"We will let you write this function. The portion you have to write can be done in three lines of code."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"deletable": false,
"id": "9kPjicpKK3YP",
"nbgrader": {
"checksum":...