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

Slides:



Advertisements
Similar presentations
Stacks, Queues, and Linked Lists
Advertisements

CATHERINE AND ANNIE Python: Part 3. Intro to Loops Do you remember in Alice when you could use a loop to make a character perform an action multiple times?
The Singleton Pattern II Recursive Linked Structures.
CS162 Week 2 Kyle Dewey. Overview Continuation of Scala Assignment 1 wrap-up Assignment 2a.
CHAPTER 10 FUN AND GAMES Group 1: Xiangling Liu.
Announcements Assignment #4 is due tonight. Last lab program is going to be assigned this Wednesday. ◦ A backtracking problem.
CompSci 100e Program Design and Analysis II March 3, 2011 Prof. Rodger CompSci 100e, Spring
1 CSC 421: Algorithm Design & Analysis Spring 2013 See online syllabus: (also on BlueLine2) Course.
Backtracking COP Backtracking  Backtracking is a technique used to solve problems with a large search space, by systematically trying and eliminating.
Backtracking What is backtracking?
Stacks.
Introduction to Stacks What is a Stack Stack implementation using array. Stack implementation using linked list. Applications of Stack.
CHAPTER 3 COLLECTIONS SET Collection. 2 Abstract Data Types A data type consists of a set of values or elements, called its domain, and a set of operators.
CHAPTER 6 Stacks Array Implementation. 2 Stacks A stack is a linear collection whose elements are added and removed from one end The last element to be.
1 Introduction to Stacks What is a Stack? Stack implementation using array. Stack implementation using linked list. Applications of Stacks.
Stacks CS-240 & CS-341 Dick Steflik. Stacks Last In, First Out operation - LIFO As items are added they are chronologically ordered, items are removed.
Week 4-5 Java Programming. Loops What is a loop? Loop is code that repeats itself a certain number of times There are two types of loops: For loop Used.
Abstract Data Types (ADTs) and data structures: terminology and definitions A type is a collection of values. For example, the boolean type consists of.
Trees, Binary Search Trees, Recursion, Project 2 Bryce Boe 2013/08/01 CS24, Summer 2013 C.
1 CSC 427: Data Structures and Algorithm Analysis Fall 2011 See online syllabus (also available through BlueLine): Course goals:
Topic 3 The Stack ADT.
Implementing Stacks Ellen Walker CPSC 201 Data Structures Hiram College.
08/10/ Iteration Loops For … To … Next. 208/10/2015 Learning Objectives Define a program loop. State when a loop will end. State when the For.
Effective Java: Generics Last Updated: Spring 2009.
Information and Computer Sciences University of Hawaii, Manoa
224 3/30/98 CSE 143 Recursion [Sections 6.1, ]
Hello.java Program Output 1 public class Hello { 2 public static void main( String [] args ) 3 { 4 System.out.println( “Hello!" ); 5 } // end method main.
Arrays An array is a data structure that consists of an ordered collection of similar items (where “similar items” means items of the same type.) An array.
Two Dimensional Arrays
1 CSC 427: Data Structures and Algorithm Analysis Fall 2010 See online syllabus (also available through BlueLine): Course goals:
CSE 373: Data Structures and Algorithms Lecture 1: Introduction; ADTs; Stacks; Eclipse.
“Planning is bringing the future into the present so that you can do something about it now.” – Alan Lakein Thought for the Day.
30/10/ Iteration Loops Do While (condition is true) … Loop.
Lexi case study (Part 2) Presentation by Matt Deckard.
Announcements This Wednesday, Class and Labs are cancelled! The last lab is due this Wednesday … how many people are planning on doing it? Finally posted.
Data Structures Using C++ 2E1 Recursion and Backtracking: DFS Depth first search (a way to traverse a tree or graph) Backtracking can be regarded as a.
Built-in Data Structures in Python An Introduction.
Control Structures II Repetition (Loops). Why Is Repetition Needed? How can you solve the following problem: What is the sum of all the numbers from 1.
Graphs – Part II CS 367 – Introduction to Data Structures.
(c) University of Washington15-1 CSC 143 Java List Implementation via Arrays Reading: 13.
Stacks and Queues. 2 3 Runtime Efficiency efficiency: measure of computing resources used by code. can be relative to speed (time), memory (space), etc.
Stacks The content for these slides was originally created by Gerard Harrison. Ported to C# by Mike Panitz.
1 Collection, Iterable, and Iterator Interfaces The Collection Interface and its Hierarchy The Iterable and Iterator Interfaces For-each Loops with Iterable.
1 CSC 427: Data Structures and Algorithm Analysis Fall 2006 See online syllabus (also available through Blackboard): Course goals:
Chapter 2 Collections. Copyright © 2004 Pearson Addison-Wesley. All rights reserved.1-2 Chapter Objectives Define the concept and terminology related.
The "8 Queens" problem Consider the problem of trying to place 8 queens on a chess board such that no queen can attack another queen. What are the "choices"?
Chapter 11Java: an Introduction to Computer Science & Programming - Walter Savitch 1 Chapter 11 l Basics of Recursion l Programming with Recursion Recursion.
CompSci 100e 6.1 Plan for the week l More recursion examples l Backtracking  Exhaustive incremental search  When we a potential solution is invalid,
Compsci 201 Recitation 10 Professor Peck Jimmy Wei 11/1/2013.
Copyright © Curt Hill Stacks An Useful Abstract Data Type.
TOWERS OF HANOI. : If n = 1, move disk 1 from pole 'A' to pole 'B'. else: 1.First, move n-1 disks from pole 'A' to pole 'C', using pole 'B' as.
CSE 373 Data Structures and Algorithms Lecture 1: Introduction; ADTs; Stacks; Eclipse.
Recursion ITI 1121 N. El Kadri. Reminders about recursion In your 1 st CS course (or its equivalent), you have seen how to use recursion to solve numerical.
List Operations CSCE 314 Spring CSCE 314 – Programming Studio Tuple and List Patterns Pattern matching with wildcards for tuples fst (a, _) = a.
Lecture 7 – Repetition (Loop) FTMK, UTeM – Sem /2014.
1 Stacks Abstract Data Types (ADTs) Stacks Application to the analysis of a time series Java implementation of a stack Interfaces and exceptions.
Copyright © 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Starting Out with Java From Control Structures through Data Structures by.
CSC 143 P 1 CSC 143 Recursion [Chapter 5]. CSC 143 P 2 Recursion  A recursive definition is one which is defined in terms of itself  Example:  Compound.
CSE 143 read: 12.5 Lecture 18: recursive backtracking.
1 Tirgul 11: Recursion & Backtracking. 2 Elements of a recursive solution (Reminder) A base case that is so simple we need no computation to solve it.
Example Program Development
IndexedListWithIteratorsViaLinear1Ind
CSCI 104 Backtracking Search
Data Structures and Algorithms
The "8 Queens" problem Consider the problem of trying to place 8 queens on a chess board such that no queen can attack another queen. What are the "choices"?
CSC 421: Algorithm Design & Analysis
Map interface Empty() - return true if the map is empty; else return false Size() - return the number of elements in the map Find(key) - if there is an.
Example Fill in the grid so that every row, column and box contains each of the numbers 1 to 9:
The "8 Queens" problem Consider the problem of trying to place 8 queens on a chess board such that no queen can attack another queen. What are the "choices"?
Presentation transcript:

1 Teaching Programming with 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...

View implements Observer

should know candidates to disallow nonsense.

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

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

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

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

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

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

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.

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

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?

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

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.

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

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

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

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

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

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

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

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

34 The functional approach

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 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` ], x /= n] box n = [x+y | x <- take 3 [row, row+9..], y <- take 3 [col..], x+y /= n ] where row = n - n `mod` 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` ], x /= n] box n = [x+y | x <- take 3 [row, row+9..], y <- take 3 [col..], x+y /= n ] where row = n - n `mod` starting row of box col = n `mod` 9 - n `mod` 3 -- starting column of box

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 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 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. C# assignments and solutions for Squiggly Sudoku are at