I'm learning Haskell and currently trying to rewrite this code:
case class Coord(x: Int, y: Int)
def buildBoard(coords: [Coord]): String = {
var str = ""
for (i <- 0 to 30) {
for (j <- 0 to 40) {
if (coords.exists(c => c.x == i && c.y == j))
str += "x "
else
str += "o "
}
str += "\n"
}
str
}
This builds a simple board for a command line game that i'm writing. What's the simplest way that I can rewrite this? I'm not looking for anything performatic
I'm using tuples to implement Coord
type:
type Coordinate = (Int, Int)
type Coordinates = [Coordinate]
I'm very grateful to those who helped. I managed to get the job done using list-comprehensions. My thought was to get rid of the "iteractive approach" of the previous code. In fact, I wanted to translate a "virtual" set of coordinates into a string, and here is how I did it:
createBoard :: Coordinates -> String
createBoard coordinates = concatMap parse board
where
parse coordinate@(_, y)
| coordinate `elem` coordinates = "x "
| y == 41 = "\n"
| otherwise = "o "
board = [ (x, y) | x <- [0..30], y <- [0..41] ]
As you already found out, the “nested loop” part can be largely done with a list comprehension.
But everything else in your original code can be done with a list comprehension as well! First use one list comprehension for the outer loop:
Prelude> [ "foo" | i <- [0..3] ]
["foo","foo","foo","foo"]
You don't really need to take care of the newline characters, simply use the standard unlines
function
Prelude> unlines [ "foo" | i <- [0..3] ]
"foo\nfoo\nfoo\nfoo\n"
Now the inner loop. This should result in a single string/list, so you could concat
the individual lists, but again there's a standard function that also adds the space that you always put between two characters:
Prelude> putStrLn $ unlines [ unwords ["a" | j <- [0..4]] | i <- [0..3] ]
a a a a a
a a a a a
a a a a a
a a a a a
And finally we can use a conditional to decide what character to use:
Prelude> putStrLn $ unlines [ unwords [if i>j then "x" else "o" | j <- [0..4]] | i <- [0..3] ]
o o o o o
x o o o o
x x o o o
x x x o o
Now let's wrap all this in a function:
createBoard :: Coordinates -> String
createBoard cs
= unlines [ unwords [ if (i,j)`elem`cs then "x" else "o"
| j <- [0..40]
]
| i <- [0..30]
]
Nested loops are what monads / do notation / list comprehensions are, in some sense.
Your task can be coded directly as one list comprehension, which will take care of all the testing, newline inserting, and concatting, corresponding pretty much directly to your "imperative" code which is not that imperative-looking if you squint a little:
board :: [(Int, Int)] -> String
board coords =
[ c | i <- [0..30], -- outer loop
j <- [0..40], -- nested loop
c <- if elem (i,j) coords
then "x " -- two chars
else "o " ++ -- spliced in
if j == 40 -- and an optional
then "\n" -- newline
else "" ]
If your language's ranges are not inclusive of the upper bound (as Haskell's "ranges", i.e. enumerations, are), you will need to adjust that here.
There's no need to use any extraneous functions except for elem
here. List comprehensions are pretty versatile. Especially the concatting is pretty much what they exist for in the first place: no matter how many nested loops there are, the element produced at the innermost level is just spliced right into the output. Just like with your str += "x"
etc. statements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With