--------------050804080601090803070903 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit My solution is 347 lines; I'll attach it below because I hate your mail gateway. :-) You can view my complete solution on the web, however. My code, unit tests, and board data are at http://www.xmission.com/~dbrady/ruby/sodoku/sodoku.tgz . All of the files in that archive are also at that url, so you can grab any of the files directly by replacing sodoku.tgz with any of: sodoku.rb, test_sodoku.rb, dbrady_solutions.txt, board1.txt, board2.txt, board3.txt, board4.txt, board5.txt, board6.txt. My strategy was to build an array of the possible moves for every row, every column and every 3x3 block, then take their intersection. I believe some other authors have already posted this concept; I'm only posting because I appear to be the only person to do it using array subtraction. :-) I find the intersection that has the fewest options available to it, and iterate over those choices, using recursion to solve the rest of the board. Using a set probably would have been better, but I didn't know about Ruby sets until after I finished my code. So my solution is a very elegant way of wielding a stone axe. :-) My poor, unaccelerated laptop clocks in under 2s in most cases, and rejects Karl von Lauderman's unsolvable puzzle in 200ms. The really difficult one takes 33-38 seconds, depending on whether my mp3 player is going. :-) One developmental note. Ruby continues to be an exceedingly pleasant language to work and design in. I wrote a complete solver with loads of unit tests, and I was pleased to discover that it could even solve Karl's unsolvable puzzle. Nifty! As I was double-checking everything, I realized that I had completely overlooked the requirement to have all the cells in a *block* be unique! (I have never heard of Sodoku before today.) I thought about it for a moment, then realized that all I needed to do was add Sodoku#possible_block_values to the class, and make sure that possible_values intersected that with the results from the row/column query. I plugged in the extra code, and discovered that my code became *faster* on account of rejecting more possibilities sooner. I added another board, that seemed interesting to me to solve: +-------+-------+-------+ | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | +-------+-------+-------+ | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | +-------+-------+-------+ | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | | _ _ _ | _ _ _ | _ _ _ | +-------+-------+-------+ Also notably absent in my code is any kind of a custom Exception for bad boards. They're not idiomatic for me so I used them very sparingly. Anyway, without further ado, my solution is attached. Cheers, -dB -- David Brady ruby_talk / shinybit.com I'm feeling really surreal today... OR AM I? --------------050804080601090803070903 Content-Type: text/plain; name odoku.rb" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename odoku.rb" #!/usr/bin/env ruby # sodoku.rb - Sodoku puzzle solver. Ruby Quiz #43. # +-------+-------+-------+ # | _ 6 _ | 1 _ 4 | _ 5 _ | # | _ _ 8 | 3 _ 5 | 6 _ _ | # | 2 _ _ | _ _ _ | _ _ 1 | # +-------+-------+-------+ # | 8 _ _ | 4 _ 7 | _ _ 6 | # | _ _ 6 | _ _ _ | 3 _ _ | # | 7 _ _ | 9 _ 1 | _ _ 4 | # +-------+-------+-------+ # | 5 _ _ | _ _ _ | _ _ 2 | # | _ _ 7 | 2 _ 6 | 9 _ _ | # | _ 4 _ | 5 _ 8 | _ 7 _ | # +-------+-------+-------+ # dbrady 2005-08-23: How my solver works: I try to find the fastest # path to the solution by considering all of the possible solutions # for each unsolved cell in the puzzle and working on the smallest # set. For example, the top-left cell (0,0), in the sample above, # only has two possible solutions: 3 and 9. All other digits are # already present elsewhere in row 0 or column 0. No cells on the # board have a single solution, so 2 is the shortest list. Many cells # on this board have 2 solutions, for arbitrariness we'll take the # first one we found. # # Now I consider all the possible solutions for that board. I create # a new board object, set that cell to a possible value, and recurse # into that board, asking it to solve itself. # # Sodoku#solution returns either a solved Sodoku object, or nil. If # the top-level Sodoku board object returns nil, the puzzle is # unsolvable. # ---------------------------------------------------------------------- # A Note on Array Subtraction # ---------------------------------------------------------------------- # This is published in the documentation for Array, but nobody ever # reads that stuff (that's why nobody ever writes any). I discovered # this by fiddling with it in irb. Array#- performs a difference # operation. Every element in the lhs that appears in the rhs is # removed. # # [2,3,4,5] - [4,5,6] [2,3] # [2,3,3,3,3,4] - [3] [2,4] # EVERY element! # # I use this differencing op to obtain array intersection thusly: # # intersection - (a-b) # # You can immediately see how this is useful, given the nature of the # puzzle. # # Anyway, if you see foo - [0], that's me clearing out all the # unsolved cells from a list of cells. # # class Sodoku - # # class Sodoku - representation of a Sodoku puzzle. A Sodoku puzzle # is a 9x9 grid, each containing numbers 1-9 such that every column # and every row contains the complete set (1..9). Stated conversely, # no column or row contains the same number twice. # # Given a partially completed Sodoku puzzle, the class can find a # solution if any exists. # # A Sodoku puzzle displays itself (using #to_s) akin to this sample: # # +-------+-------+-------+ # | _ 6 _ | 1 _ 4 | _ 5 _ | # | _ _ 8 | 3 _ 5 | 6 _ _ | # | 2 _ _ | _ _ _ | _ _ 1 | # +-------+-------+-------+ # | 8 _ _ | 4 _ 7 | _ _ 6 | # | _ _ 6 | _ _ _ | 3 _ _ | # | 7 _ _ | 9 _ 1 | _ _ 4 | # +-------+-------+-------+ # | 5 _ _ | _ _ _ | _ _ 2 | # | _ _ 7 | 2 _ 6 | 9 _ _ | # | _ 4 _ | 5 _ 8 | _ 7 _ | # +-------+-------+-------+ # # The board rows and columns are numbered 0-8. class Sodoku attr_accessor :board # initialize - ctor. Takes 1 arg, which is either a string # containing the representation of the puzzle, or another Sodoku # object, in which case we make a copy of its @board member. def initialize(str il) @board ] 9.times do |y| @board[y] 0,0,0,0,0,0,0,0,0] end self.load(str) if str.class String if str.respond_to? :board self.copy_board(str) end end # returns true if this board has no duplicates in any row or column. def valid? valid rue return false unless @board.length 9 @board.each do |row| return false unless row.length 9 row.each do |cell| return false unless cell.class Fixnum && cell<& cell>