I\'m stuck at doing my homework. I have to write a function that has 2 [String]. List string contains 3 chars:
I will give you an overview, and allow myself to leave some details unpolished. Please adjust my advice to your liking.
My answer will be structured like this:
In real life my process is "dialectic", and all these lines of thought grow simultaneously, by trial and error.
I am thinking that, given two fields with some pieces each, I can always put these fields "on top of each other", so that every piece is found in the same place in the received field as it was in one of the given fields. (Unless there are two pieces on the same place, in which case the behaviour is undefined.) Once I can add two fields this way, I can add any number thereof. And it should not be too hard to produce a field with a single piece. This technique is called "folding a monoid" and you will see it used a lot in Haskell.
This is how I will solve this problem:
getPiece
function to read a piece.putPiece
function to display a field with one piece.overlay
function that overlays any two fields.type Piece = (Char, Int, Int) -- Piece, row, column.
type Field = [String] -- Rows.
getPiece :: String -> Piece
putPiece :: Piece -> Field
overlay :: Field -> Field -> Field
chess :: [String] -> [String] -> Field
It may be worth your time to take a piece of paper and draw some pictures of how these types and functions may possibly connect.
getPiece :: String -> Piece
getPiece [v, x, y] = (piece, row, column)
where
piece = v
row = (Char.ord y - 48)
column = (Char.ord x - 96)
putPiece :: Piece -> Field
putPiece (v, x, y) = reverse
$ replaceAt (x - 1) (replaceAt (y - 1) v blank) (replicate 8 blank)
where
blank = replicate 8 ' '
replaceAt :: Int -> a -> [a] -> [a]
replaceAt i y xs =
let (before, (_: after)) = List.splitAt i xs
in before ++ y: after
overlay :: Field -> Field -> Field
overlay u v = zipWith (zipWith overlayOne) u v
where
overlayOne ' ' y = y
overlayOne x _ = x
chess :: [String] -> [String] -> Field
chess white black = List.foldl1' overlay . fmap putPiece $ pieces
where
pieces = fmap (makeWhite . getPiece) white ++ fmap getPiece black
makeWhite :: Piece -> Piece
makeWhite (c, x, y) = (Char.toLower c, x, y)
A tricky part here is how two zipWith
functions are combined to achieve a "zip" effect on a
list of lists. Notice also that I am not hesitating to define a helper function replaceAt
when I
think it will make the main function really simple.
I find it to be most comfortable to approach even a simple problem with the right toolbox of
abstractions. In this case, we make use of a monoid (defined by overlay
) and a recursion
scheme (List.foldl'
is an instance of "catamorphism"). I am sure you will
meet many more cases in your programming practice where these ideas can be put to use.
Leave me a comment if something is not approachable or poorly explained.
Enjoy Haskell!
P.S. See also another, algorithmically faster approach to a very similar problem.