1 / 40

Teaching Programming with Sudoku

Teaching Programming with Sudoku. Bill Sanders for Axel T. Schreiner Killer Examples Workshop at OOPSLA’09. The problem The exercise yard The path to hell... The functional approach The references. The problem. Each row, column, and box must contain all n digits. 3. The problem.

tanaya
Download Presentation

Teaching Programming with Sudoku

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

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

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

  5. The exercise yard

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

  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. Solving assistant (1) • Show entered digits, candidates. • dynamically constructed GUI • reusable GUI parts, e.g. digit view vs. candidate view

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

  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. The path to hell...

  12. View implements Observer

  13. View implements Observer

  14. View implements Observer • should know candidates to disallow nonsense.

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

  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); }

  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* *Half-Baked Programming • model the digits on the board... 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. */ finalint row, col; /** digit in this position. */ finalint[] digits; Move (int row, int col, int digit) { ... } boolean equals (int digit) { return digits[0] == digit; } boolean isKnown () { returntrue; } boolean canBe (int digit) { returnfalse; } int[] digits () { return digits; } boolean prune (int digit) { returnfalse; } }

  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 { finalint row, col; int[] digits; final BitSet canBe = new BitSet(dim+1); Digits (int row, int col) { ... search ... } boolean equals (int digit) { returnfalse; } 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)) returnfalse; 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); } 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 Model { void addObserver (Observer observer) { ... } void set (int row, int col, int digit) { ... move|ok ... } void undo () { ... queues ... } // ...

  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); • 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); class Move ... { return digit; throw ...; return true; for (c: context) c.infer0(digit); digits.clear(digit);

  27. 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); 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);

  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; }

  29. row iterator abstractclass 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; } /** for (int c = 0; c < dim; ++ c) return board[row][c] */ Loop doRow (finalint row) { returnnew Loop() { Loop copy () { return doRow(row); } Digit next () { if (!hasNext()) thrownew NoSuchElementException(); return board[row][n++]; } };}

  30. context iterator Loop doContext (finalint row, finalint col) { final Loop[] loop = { doRow(row), doColumn(col), doBox(row, col) }; returnnew 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); }

  32. Digits.pair (Move.no-op) boolean pair () { if (digits.cardinality() != 2) returnfalse; 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) { returnthis.digits.equals(digits); }

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

  34. The functional approach

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

  36. Geometry 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 • Puzzle is a list of 81 digits, zero if not known. • Geometry is described as lists of indices, using infinite lists for generation.

  37. Candidates and moving 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 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.

  38. Possibilities and choices moves = zip (repeat zero) (candidates sudoku zero) where zero = length $ takeWhile (0 /=) sudoku choices sudoku = map move moves • 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.

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

  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/

More Related