Presentation is loading. Please wait.

Presentation is loading. Please wait.

1 Teaching Programming with Sudoku Bill Sanders for Axel T. Schreiner Killer Examples Workshop at OOPSLA’09.

Similar presentations


Presentation on theme: "1 Teaching Programming with Sudoku Bill Sanders for Axel T. Schreiner Killer Examples Workshop at OOPSLA’09."— Presentation transcript:

1 1 Teaching Programming with Sudoku Bill Sanders for Axel T. Schreiner Killer Examples Workshop at OOPSLA’09

2 2 The problem The exercise yard The path to hell... The functional approach The references

3 3 The problem Each row, column, and box must contain all n digits. 3

4 4 The problem Each row, column, and box must contain all n digits. 4

5 5 The exercise yard

6 6 Verifier Compare a solution to a puzzle. array slicing (row, column, box) arbitrary shapes (composition) Very cool exercise for XSLT.

7 7 Model Represent puzzle state, perform changes. grid of cells holding digit or candidates information hiding principles cell interface, state classes Good exercise to test from command line.

8 8 Solving assistant (1) Show entered digits, candidates. dynamically constructed GUI reusable GUI parts, e.g. digit view vs. candidate view

9 9 Solving assistant (2) Nice playground for design patterns. MVC several views observe one model undo/redo pattern factories to add features

10 10 Solver Solve using heuristics and/or backtracking. functional approach iterators for row/column/box composed iterator for context brute-force backtracking Perfect for a functional language.

11 11 The path to hell...

12 View implements Observer

13

14 should know candidates to disallow nonsense.

15 View implements Observer should know candidates to disallow nonsense. also need to infer from singletons.

16 Model extends Observable void set (row, col, digit) int[] get (row, col) undo() redo()... pruning algorithms, solver?

17 get set could tell row, column, box but after undo cell needs to ask row, col, box: search: for (int digit = 1; digit <= dim; ++ digit) { for (int c = 0; c < board[row].length; ++ c) if (c != col && board[row][c].equals(digit)) continue search; for (int r = 0; r < board.length; ++ r) if (r != row && board[r][col].equals(digit)) continue search; int r = (row/boxDim)*boxDim, c = (col/boxDim)*boxDim; for (int i = 0; i < boxDim; ++ i) for (int j = 0; j < boxDim; ++ j) if ((r+i != row || c+j != col) && board[r+i][c+j].equals(digit)) continue search; canBe.set(digit); } search: for (int digit = 1; digit <= dim; ++ digit) { for (int c = 0; c < board[row].length; ++ c) if (c != col && board[row][c].equals(digit)) continue search; for (int r = 0; r < board.length; ++ r) if (r != row && board[r][col].equals(digit)) continue search; int r = (row/boxDim)*boxDim, c = (col/boxDim)*boxDim; for (int i = 0; i < boxDim; ++ i) for (int j = 0; j < boxDim; ++ j) if ((r+i != row || c+j != col) && board[r+i][c+j].equals(digit)) continue search; canBe.set(digit); }

18 singles — optional if get().length == 1 could run search until "nothing changes"... but how to keep the information? and how to undo ?

19 HBP * model the digits on the board... * Half-Baked Programming interface Digit { /** true if digit is result of move. */ boolean equals (int digit); /** true if the digit in the position is set or inferred. */ boolean isKnown (); /** true if digit could be in position. */ boolean canBe (int digit); /** result of get. */ int[] digits (); /** remove single digit from candidates if possible, return false or isKnown. */ boolean prune (int digit); } interface Digit { /** true if digit is result of move. */ boolean equals (int digit); /** true if the digit in the position is set or inferred. */ boolean isKnown (); /** true if digit could be in position. */ boolean canBe (int digit); /** result of get. */ int[] digits (); /** remove single digit from candidates if possible, return false or isKnown. */ boolean prune (int digit); }

20 HBP * * Half-Baked Programming class Move implements Digit { /** position on board. */ final int row, col; /** digit in this position. */ final int[] digits; Move (int row, int col, int digit) {... } boolean equals (int digit) { return digits[0] == digit; } boolean isKnown () { return true; } boolean canBe (int digit) { return false; } int[] digits () { return digits; } boolean prune (int digit) { return false; } } class Move implements Digit { /** position on board. */ final int row, col; /** digit in this position. */ final int[] digits; Move (int row, int col, int digit) {... } boolean equals (int digit) { return digits[0] == digit; } boolean isKnown () { return true; } boolean canBe (int digit) { return false; } int[] digits () { return digits; } boolean prune (int digit) { return false; } }

21 undo set pushes each Move on the undo stack and clears the redo stack. undo pops a Move, pushes it on the redo stack, and removes the Move from the board. redo pops a Move and performs like set. freeze discards both stacks, e.g., to mark a puzzle or the begin of backtracking.

22 HBP * * Half-Baked Programming class Digits implements Digit { final int row, col; int[] digits; final BitSet canBe = new BitSet(dim+1); Digits (int row, int col) {... search... } boolean equals (int digit) { return false; } boolean isKnown () { canBe.cardinality() == 1; } boolean canBe (int digit) { return canBe.get(digit); } int[] digits () { if (digits == null) {... convert from canBe... } return digits; } boolean prune (int digit) { if (!canBe.get(digit)) return false; digits = null; canBe.clear(digit); return isKnown(); } class Digits implements Digit { final int row, col; int[] digits; final BitSet canBe = new BitSet(dim+1); Digits (int row, int col) {... search... } boolean equals (int digit) { return false; } boolean isKnown () { canBe.cardinality() == 1; } boolean canBe (int digit) { return canBe.get(digit); } int[] digits () { if (digits == null) {... convert from canBe... } return digits; } boolean prune (int digit) { if (!canBe.get(digit)) return false; digits = null; canBe.clear(digit); return isKnown(); }

23 Problems Inferring single digits can be done but it is messy and looks impossible to extend View receives update and uses get for state but get does not distinguish moves from inferred singles Where did the design fail?

24 OOP interface Observer { /** user's move. */ void move (int row, int col, int digit); /** candidates. */ void ok (int row, int col, BitSet digits); /** change in undo/redo. */ void queues (int undos, int redos); } interface Observer { /** user's move. */ void move (int row, int col, int digit); /** candidates. */ void ok (int row, int col, BitSet digits); /** change in undo/redo. */ void queues (int undos, int redos); } class Model { void addObserver (Observer observer) {... } void set (int row, int col, int digit) {... move|ok... } void undo () {... queues... } //... class Model { void addObserver (Observer observer) {... } void set (int row, int col, int digit) {... move|ok... } void undo () {... queues... } //... class View implements Observer { // very passive... void move (int row, int col, int digit) {... JLabel... } void ok (int row, int col, BitSet digits) {... JList... } class View implements Observer { // very passive... void move (int row, int col, int digit) {... JLabel... } void ok (int row, int col, BitSet digits) {... JList... }

25 OOP interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits); interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits); model move and candidates. host inference algorithms.

26 OOP interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits); interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits); class Move... { return digit; throw...; return true; for (c: context) c.infer0(digit); digits.clear(digit); class Move... { return digit; throw...; return true; for (c: context) c.infer0(digit); digits.clear(digit);

27 class Digits... { throw...; return digits; return digits.cardinality() == 1; digits.clear(digit); ok(row, col, digits); for (c: context) c.infer1(newDgts); ok(row, col, newDgts); class Digits... { throw...; return digits; return digits.cardinality() == 1; digits.clear(digit); ok(row, col, digits); for (c: context) c.infer1(newDgts); ok(row, col, newDgts); OOP interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits); interface Digit { /** digit set in position. */ int digit (); /** candidates in position. */ BitSet digits (); /** true if single digit. */ boolean isKnown (); /** prune context. */ void infer0 (); /** digit is no candidate! */ void infer0 (int digit); /** compute candidates. */ void infer1 (); /** move is no candidate! */ void infer1 (BitSet digits);

28 Digits.single (Move.no-op) /** if this is a singleton, tell the others. */ void single () { if (digits.cardinality() == 1) { Digit d; for (Loop i = doContext(row, col); i.hasNext(); ) if ((d = i.next()) != this) d.single(digits); } } /** recurse if this changes into a singleton. */ boolean single (BitSet digits) { if (!this.digits.intersects(digits)) return false; this.digits.andNot(digits); ok(row, col); single(); return true; } /** if this is a singleton, tell the others. */ void single () { if (digits.cardinality() == 1) { Digit d; for (Loop i = doContext(row, col); i.hasNext(); ) if ((d = i.next()) != this) d.single(digits); } } /** recurse if this changes into a singleton. */ boolean single (BitSet digits) { if (!this.digits.intersects(digits)) return false; this.digits.andNot(digits); ok(row, col); single(); return true; }

29 row iterator /** for (int c = 0; c < dim; ++ c) return board[row][c] */ Loop doRow (final int row) { return new Loop() { Loop copy () { return doRow(row); } Digit next () { if (!hasNext()) throw new NoSuchElementException(); return board[row][n++]; } };} /** for (int c = 0; c < dim; ++ c) return board[row][c] */ Loop doRow (final int row) { return new Loop() { Loop copy () { return doRow(row); } Digit next () { if (!hasNext()) throw new NoSuchElementException(); return board[row][n++]; } };} abstract class Loop { /** state of the loop. */ int n = 0; /** deep copy with n reset to zero. */ abstract Loop copy (); /** default: n < dim. */ boolean hasNext () { return n < dim; } /** next item in the loop. */ abstract Digit next () throws NoSuchElementException; } abstract class Loop { /** state of the loop. */ int n = 0; /** deep copy with n reset to zero. */ abstract Loop copy (); /** default: n < dim. */ boolean hasNext () { return n < dim; } /** next item in the loop. */ abstract Digit next () throws NoSuchElementException; }

30 context iterator Loop doContext (final int row, final int col) { final Loop[] loop = { doRow(row), doColumn(col), doBox(row, col) }; return new Loop() { Loop copy () { return doContext(row, col); } boolean hasNext () { return loop[n].hasNext(); } Digit next () { Digit result = loop[n].next(); if (!loop[n].hasNext() && n < loop.length-1) ++ n; return result; } }; } Loop doContext (final int row, final int col) { final Loop[] loop = { doRow(row), doColumn(col), doBox(row, col) }; return new Loop() { Loop copy () { return doContext(row, col); } boolean hasNext () { return loop[n].hasNext(); } Digit next () { Digit result = loop[n].next(); if (!loop[n].hasNext() && n < loop.length-1) ++ n; return result; } }; }

31 Digits.unique (Move.no-op) boolean unique () { if (digits.cardinality() <= 1) return false; Loop[] loop = {doRow(row), doColumn(col), doBox(row, col)}; for (int i = 0; i < loop.length; ++ i) { // next = digits minus row/column/box BitSet next = (BitSet)digits.clone(); Digit d; while (loop[i].hasNext()) if ((d = loop[i].next()) != this) d.unique(next); // unique digit left? if (next.cardinality() == 1 && next.intersects(digits)) { // ok.. turn into singleton and tell others digits = next; ok(row, col); single(); return true; } } } /** clear this.digits in the incoming digits. */ void unique (BitSet digits) { digits.andNot(this.digits); } boolean unique () { if (digits.cardinality() <= 1) return false; Loop[] loop = {doRow(row), doColumn(col), doBox(row, col)}; for (int i = 0; i < loop.length; ++ i) { // next = digits minus row/column/box BitSet next = (BitSet)digits.clone(); Digit d; while (loop[i].hasNext()) if ((d = loop[i].next()) != this) d.unique(next); // unique digit left? if (next.cardinality() == 1 && next.intersects(digits)) { // ok.. turn into singleton and tell others digits = next; ok(row, col); single(); return true; } } } /** clear this.digits in the incoming digits. */ void unique (BitSet digits) { digits.andNot(this.digits); }

32 Digits.pair (Move.no-op) boolean pair () { if (digits.cardinality() != 2) return false; boolean result = false; Digit that, d; Loop[] loop = { doRow(row), doColumn(col), doBox(row, col) }; for (int i = 0; i < loop.length; ++ i) while (loop[i].hasNext()) if ((that = loop[i].next()) != this && that.pair(digits)) for (Loop j = loop[i].copy(); j.hasNext(); ) if ((d = j.next()) != this && d != that) result |= d.single(digits); // prune return result; } /** true if this.digits and incoming digits are the same. */ boolean pair (BitSet digits) { return this.digits.equals(digits); } boolean pair () { if (digits.cardinality() != 2) return false; boolean result = false; Digit that, d; Loop[] loop = { doRow(row), doColumn(col), doBox(row, col) }; for (int i = 0; i < loop.length; ++ i) while (loop[i].hasNext()) if ((that = loop[i].next()) != this && that.pair(digits)) for (Loop j = loop[i].copy(); j.hasNext(); ) if ((d = j.next()) != this && d != that) result |= d.single(digits); // prune return result; } /** true if this.digits and incoming digits are the same. */ boolean pair (BitSet digits) { return this.digits.equals(digits); }

33 OOP Lessons information hiding. if instanceof considered harmful. distribute algorithm through messages. divide and conquer. check if existing classes really suffice.

34 34 The functional approach

35 35 Backtracking Brute-force backtracker in Haskell, requires solved : true if done choices : possible new puzzles created from a given situation solve puzzle | solved puzzle = Just puzzle | otherwise = case filter (/= Nothing) attempts of [] -> Nothing (x:xs) -> x where attempts = map solve (choices puzzle) solve puzzle | solved puzzle = Just puzzle | otherwise = case filter (/= Nothing) attempts of [] -> Nothing (x:xs) -> x where attempts = map solve (choices puzzle)

36 36 Geometry Puzzle is a list of 81 digits, zero if not known. Geometry is described as lists of indices, using infinite lists for generation. solved sudoku = 0 `notElem` sudoku context n = row n ++ col n ++ box n row n = take 8 [x | x <- [n - n `mod` 9..], x /= n] col n = take 8 [x | x <- [n `mod` 9, n `mod` 9 + 9..], x /= n] box n = [x+y | x <- take 3 [row, row+9..], y <- take 3 [col..], x+y /= n ] where row = n - n `mod` 27 -- starting row of box col = n `mod` 9 - n `mod` 3 -- starting column of box solved sudoku = 0 `notElem` sudoku context n = row n ++ col n ++ box n row n = take 8 [x | x <- [n - n `mod` 9..], x /= n] col n = take 8 [x | x <- [n `mod` 9, n `mod` 9 + 9..], x /= n] box n = [x+y | x <- take 3 [row, row+9..], y <- take 3 [col..], x+y /= n ] where row = n - n `mod` 27 -- starting row of box col = n `mod` 9 - n `mod` 3 -- starting column of box

37 37 Candidates and moving Candidates are digits not in the context of a cell — simply prune from all. A move is a digit and an index — simply copy the array and replace the digit at the position. candidates sudoku cell = [digit | digit <- [1..9], safe digit] where safe digit = digit `notElem` [sudoku!!x | x <- context cell] move (position, choice) = map choose (zip sudoku [0..]) where choose (digit, index) | position == index = choice | otherwise = digit candidates sudoku cell = [digit | digit <- [1..9], safe digit] where safe digit = digit `notElem` [sudoku!!x | x <- context cell] move (position, choice) = map choose (zip sudoku [0..]) where choose (digit, index) | position == index = choice | otherwise = digit

38 38 Possibilities and choices zero is the index of the first unknown cell. Possible moves combine this index with each candidate digit for the cell. New puzzles result by making every possible move in the situation. Haskell computes by lazy evaluation. moves = zip (repeat zero) (candidates sudoku zero) where zero = length $ takeWhile (0 /=) sudoku choices sudoku = map move moves moves = zip (repeat zero) (candidates sudoku zero) where zero = length $ takeWhile (0 /=) sudoku choices sudoku = map move moves

39 39 Notation and thought Extensive syntax gets in the way. Boilerplate clogs the mind. Structures should be light-weight.

40 40 The references The extended abstract references a paper and a number of assignments and solutions. http://www.cs.rit.edu/~ats/papers/sudoku2/sudoku2.pdf C# assignments and solutions for Squiggly Sudoku are at http://www.cs.rit.edu/~ats/cs-2009-1/


Download ppt "1 Teaching Programming with Sudoku Bill Sanders for Axel T. Schreiner Killer Examples Workshop at OOPSLA’09."

Similar presentations


Ads by Google