HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Solve a maze in the form of a 2D array using BFS - Haskell

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thearraymazebfssolveusinghaskellform

Problem

Suggestions for improving coding style are greatly appreciated.

```
import qualified Data.List as L
import qualified Data.Map.Strict as M
import qualified Data.Vector as V

type Queue a = ([a], [a])

emptyQueue = ([], [])

pushListToAnother fromLst toLst = L.foldl' (\ys x -> (x:ys)) toLst fromLst

enqueue :: Queue a -> a -> Queue a
enqueue (inList, outList) x = ((x:inList), outList)

dequeue :: Queue a -> Maybe (a, Queue a)
dequeue (inList, outList) = case outList of
(y:ys) -> Just (y, (inList, ys))
[] -> if (null inList) then Nothing else dequeue ([], reverse inList)

massEnqueue :: Queue a -> [a] -> Queue a
massEnqueue (inList, outList) items = ((pushListToAnother items inList), outList)

-- consider moving the above Queue code into a separate module.

type Grid a = V.Vector (V.Vector a)
type Indices = (Int, Int)

access grid (x, y) = (grid V.! x) V.! y

massInsert :: Ord k => [(k, v)] -> M.Map k v -> M.Map k v
massInsert elems theMap = L.foldl' (\m (k, v) -> M.insert k v m) theMap elems

validAndTraversable :: (a -> Bool) -> Grid a -> Indices -> Bool
validAndTraversable traversability grid xy@(x, y) = let xbound = V.length grid in
let ybound = V.length (V.head grid) in
let withinBounds = (x >= 0) && (x = 0) && (y M.Map a a -> a -> a -> [a]
getPath visitedFromMap start current = pathHelper visitedFromMap start current []
where pathHelper prevIndicesMap start current path = let newPath = (current:path) in
if current == start
then newPath
else case (M.lookup current prevIndicesMap) of
Nothing -> []

Solution

Some general ideas:

  • Always give types to top level functions. It improves readability of your code very much.



  • Unless you really need to, it's better to use existing data structures than inventing your own. Instead of using Queue, you could as well use Data.Sequence.



-
Some of the Queue functions can be simplified, often using first from Control.Arrow:

pushListToAnother :: [a] -> [a] -> [a]
pushListToAnother fromLst = (reverse fromLst ++)

enqueue :: Queue a -> a -> Queue a
enqueue q x = first (x :) q

massEnqueue :: Queue a -> [a] -> Queue a
massEnqueue q items = first (pushListToAnother items) q


-
Strictly adhere to having some maximum number of columns (often people use 72, 78 or 80). Code that goes too far to the right is very much unreadable.

-
Instead of let f = foo in let g = bar in boo use just let f = foo ; g = bar in boo. For example:

validAndTraversable :: (a -> Bool) -> Grid a -> Indices -> Bool
validAndTraversable traversability grid xy@(x, y) =
    let xbound = V.length grid
        ybound = V.length (V.head grid)
        withinBounds = (x >= 0) && (x = 0) && (y < ybound)
     in withinBounds && (traversability (access grid xy))


-
Prefer guards over if/then/else. Usually it leads to more concise code and it's more idiomatic. Pattern guards can be even more helpful.

getPath :: Ord a => M.Map a a -> a -> a -> [a]
getPath visitedFromMap start current =
    pathHelper visitedFromMap start current []
  where
    pathHelper prevIndicesMap start current path
        | current == start
            = newPath
        | Just e <- M.lookup current prevIndicesMap
            = (pathHelper prevIndicesMap start e) $! newPath
        | otherwise
            = []
      where newPath = (current:path)


-
Avoid code repetition, for example multiple calls to the same function, if you can factor the call out:

maze1 = V.fromList . map V.fromList $
    [ [1,1,1,1,1,1,0]
    , [0,0,0,0,0,0,0]
    , [1,1,1,1,1,1,0]
    , [0,0,0,0,0,0,0]
    , [0,1,1,1,1,1,1]
    , [0,0,0,0,0,0,0]
    , [1,1,1,0,1,1,1]
    , [0,0,0,0,0,0,0]
    , [0,1,1,1,1,1,0]
    ]


-
Use hlint, it'll give you a lot of useful hints.

Code Snippets

pushListToAnother :: [a] -> [a] -> [a]
pushListToAnother fromLst = (reverse fromLst ++)

enqueue :: Queue a -> a -> Queue a
enqueue q x = first (x :) q

massEnqueue :: Queue a -> [a] -> Queue a
massEnqueue q items = first (pushListToAnother items) q
validAndTraversable :: (a -> Bool) -> Grid a -> Indices -> Bool
validAndTraversable traversability grid xy@(x, y) =
    let xbound = V.length grid
        ybound = V.length (V.head grid)
        withinBounds = (x >= 0) && (x < xbound) && (y >= 0) && (y < ybound)
     in withinBounds && (traversability (access grid xy))
getPath :: Ord a => M.Map a a -> a -> a -> [a]
getPath visitedFromMap start current =
    pathHelper visitedFromMap start current []
  where
    pathHelper prevIndicesMap start current path
        | current == start
            = newPath
        | Just e <- M.lookup current prevIndicesMap
            = (pathHelper prevIndicesMap start e) $! newPath
        | otherwise
            = []
      where newPath = (current:path)
maze1 = V.fromList . map V.fromList $
    [ [1,1,1,1,1,1,0]
    , [0,0,0,0,0,0,0]
    , [1,1,1,1,1,1,0]
    , [0,0,0,0,0,0,0]
    , [0,1,1,1,1,1,1]
    , [0,0,0,0,0,0,0]
    , [1,1,1,0,1,1,1]
    , [0,0,0,0,0,0,0]
    , [0,1,1,1,1,1,0]
    ]

Context

StackExchange Code Review Q#41503, answer score: 4

Revisions (0)

No revisions yet.