Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are good techniques for rewriting nested for loops in Haskell?

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]

Possible answer

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] ]
like image 327
Augusto Dias Avatar asked Sep 05 '25 16:09

Augusto Dias


2 Answers

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]
           ]
like image 102
leftaroundabout Avatar answered Sep 07 '25 17:09

leftaroundabout


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.

like image 24
Will Ness Avatar answered Sep 07 '25 16:09

Will Ness