Fun with Haskell, runST, MArray, and a few queens.

Hello Enthusiasts, My fiancee was assigned the n-queens problem in her Data Structures class. It was a study in backtracking. For those unfamiliar with the problem: one is given a grid of n x n. Return a grid with n queens on it where no queen can be attacked by another. Anyway, I decided to try an implementation in Haskell (as I often do with her assignments). Instead of the imperative approach (adding a queen and then getting rid of it), I opted for a functional one (the grid is passed to recursive calls, etc.). The interesting thing about this assignment is the runtimes: (n=10) ghc 58.749s ghc -O 12.580s javac 1.088s The Haskell version takes significantly longer (and it gets worse for larger inputs). So it seems that imperative algorithms are much better for certain problems. Since Haskell is supposed to have the ability to run imperative algorithms, I was wondering if any of you could explain how runST and MArray could be used to solve this problem (or is there a better way?). I am also interested in the run times you get with these two implementations of the n-queens problem. David module Main where import Array import Maybe boardSizeToTest = 10 main = print $ fromJust $ solution (emptyBoard boardSizeToTest) boardSizeToTest --The Board datatype. A n x n array indexed starting with 1. data Board = Board Int (Array (Int,Int) Int) deriving Eq instance Show Board where show (Board n b) = concat [ (printLine b a) ++ "\n" | a <- [n,n-1..1] ] where printLine board row = concat [ (str (board ! (row,a))) | a <- [1..n] ] str (-1) = "q" --It's a queen str 0 = "." --It's an empty spot str a = show a --It's a spot that can be attacked by a queen --Helper function from Gentle Introduction to Haskell mkArray :: (Ix a) => (a -> b) -> (a,a) -> Array a b mkArray f bnds = array bnds [(i, f i) | i <- range bnds] --Another helper function to update arrays (/-) :: (Ix a) => Array a b -> [(a, (b -> b))] -> Array a b (/-) array s = array // (map (\ (a, f) -> (a,f (array!a))) s) --An empty chessboard emptyBoard :: Int -> Board emptyBoard n = Board n $ mkArray (\_->0) ((1,1),(n,n)) --Adds a queen to the board and adds 1 in all the positions the queen --could feasible move. addQueen :: Board -> (Int,Int) -> Board addQueen b@(Board n board) c = Board n $ newBoard // [(c, -1)] where Board _ newBoard = queenHelper b (+1) c --Removes a queen from the board and subtracts 1 in all the positions the queen --could have feasible moved. removeQueen :: Board -> (Int,Int) -> Board removeQueen b@(Board n board) c = Board n $ newBoard // [(c, 0)] where Board _ newBoard = queenHelper b ((-)1) c queenHelper :: Board -> (Int->Int) -> (Int, Int) -> Board queenHelper (Board n board) f (row,column) = Board n $ board /- horizontal /- vertical /- negPosDiag /- posNegDiag /- negNegDiag /- posPosDiag where vertical = [ ((r,column),f) | r <- [1..n] ] horizontal = [ ((row,c),f) | c <- [1..n] ] negPosDiag = [ ((row-i,column+i),f)| i <- [1..(min (n-column) (row-1))]] posNegDiag = [ ((row+i,column-i),f)| i <- [1..(min (column-1) (n-row))]] negNegDiag = [ ((row-i,column-i),f)| i <- [1..(min (column-1) (row-1))]] posPosDiag = [ ((row+i,column+i),f)| i <- [1..(min (n-column) (n-row))]] --Returns all the positions on the board that do not have a queen and cannot --be attacked by a queen already on the board. possiblePositions (Board n board) = filter (\a -> board ! a == 0) [ (row,column) | row <- [1..n], column <- [1..n] ] --Finds a solution given a board and a number of queens to put on it solution :: Board -> Int -> Maybe Board solution board 0 = Just board solution board i = let solutions = catMaybes $ [ solution (addQueen board c) (i-1) | c <- (possiblePositions board) ] in if solutions == [] then Nothing else Just $ head solutions

Try this Queens.hs module Main where main = print $ queens 10 boardSize = 10 queens 0 = [[]] queens n = [ x : y | y <- queens (n-1), x <- [1..boardSize], safe x y 1] where safe x [] n = True safe x (c:y) n = and [ x /= c , x /= c + n , x /= c - n , safe x y (n+1)] Copied from somebody else. -----Original Message----- From: haskell-cafe-bounces@haskell.org [mailto:haskell-cafe-bounces@haskell.org]On Behalf Of David Sankel Sent: Thursday, March 04, 2004 12:19 PM To: haskell-cafe@haskell.org Subject: [Haskell-cafe] Fun with Haskell, runST, MArray, and a few queens. Hello Enthusiasts, My fiancee was assigned the n-queens problem in her Data Structures class. It was a study in backtracking. For those unfamiliar with the problem: one is given a grid of n x n. Return a grid with n queens on it where no queen can be attacked by another. Anyway, I decided to try an implementation in Haskell (as I often do with her assignments). Instead of the imperative approach (adding a queen and then getting rid of it), I opted for a functional one (the grid is passed to recursive calls, etc.). The interesting thing about this assignment is the runtimes: (n=10) ghc 58.749s ghc -O 12.580s javac 1.088s The Haskell version takes significantly longer (and it gets worse for larger inputs). So it seems that imperative algorithms are much better for certain problems. Since Haskell is supposed to have the ability to run imperative algorithms, I was wondering if any of you could explain how runST and MArray could be used to solve this problem (or is there a better way?). I am also interested in the run times you get with these two implementations of the n-queens problem. David

David Sankel wrote:
Try this Queens.hs
Thanks for the program, but how does one decipher the output?
The Nth item in each list is the column of the queen which is in row N
(or the row of the queen which is in column N; the transpose of a
valid solution must also be a valid solution).
If you prefer (row, column) pairs, use e.g.:
main = print $ map (zip [1..]) $ queens 10
--
Glynn Clements

On Thu, Mar 04 2004, David Sankel wrote:
The Haskell version takes significantly longer (and it gets worse for larger inputs). So it seems that imperative algorithms are much better for certain problems.
I say this is a case of bad code. Of course language <foo> is faster and better if you write horribly bad code in language <bar>. Taking the first solution found by searching with google I get times around 0.015s (real) for the Haskell version and 1.7s for your Java solution (which also seems to be overcomplicated to me). /Hampus -- Homepage: http://www.dtek.chalmers.se/~d00ram E-mail: d00ram@dtek.chalmers.se "Det är aldrig försent att ge upp"

G'day all.
Quoting Hampus Ram
I say this is a case of bad code. Of course language <foo> is faster and better if you write horribly bad code in language <bar>.
Good link from LtU: http://www.deftcode.com/archives/every_language_war_ever.html Any direct literal translation of a program from one language to another is probably going to lose something (at least readability, probably also efficiency) unless the languages are similar. Cheers, Andrew Bromage
participants (5)
-
ajb@spamcop.net
-
David Sankel
-
Glynn Clements
-
Hampus Ram
-
Michael Wang