Okay, I'm new to Haskell, and need help getting my head around functional approaches to problems.
I have a list of list. More specifically [[Char]].
I would like to target a specific element in a list, and look at the elements above, below, and to the left and right of the element.
For example:
["-------",
"-------",
"---N---",
"--WOE--",
"---S---",
"-------",
"-------"]
Where O is the element I'm targeting, and N,S,E,W are the elements directly surrounding the element O.
I'm made a function that gets an (x,y) coordinate of the element O, and it is as follows:
find :: Eq a => [[a]] -> a -> (Int,Int)
find [[]] _ = (-1,-1)
find (x:xs) el = findHelper (x:xs) (0,0,el)
findHelper :: Eq a => [[a]] -> (Int,Int,a) -> (Int,Int)
findHelper [[]] _ = (-1,-1)
findHelper (x:xs) (row,col,el)
| x == [] = findHelper xs (row+1,0,el)
| (head x) == el = (row,col)
| otherwise = findHelper ((tail x) : xs) (row,col + 1,el)
This (x,y) coordinate system is 0-indexed. I just can't for the life of me figure out once I have the position of the element I'm looking for, how to figure out what elements are surrounding it.
Sorry if this isn't the most clear of questions, and I can elaborate more if I missed something.
What you are trying to do is not always possible, since lists don't have fixed sizes. If you can ensure that however, it is possible to do this, otherwise you will need a lot of checks to prevent problems. I'll show a way you can get the surrounding elements, but you will have to build in some extra checks to make sure it does not try to lookup an element that is out of bounds for example.
This should get you a list with the surrounding elements:
findSurrounding :: [[a]] -> (Int, Int) -> [a]
findSurrounding matrix (x,y) = map (getElementAt matrix) positions
where positions = [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]
getElementAt :: [[a]] -> (Int, Int) -> a
getElementAt matrix (x, y) = (matrix !! y) !! x
I have not tested the code so it might need some tweaking, but it should get you started.
Related
deadNeighbors:
Give a list of empty cells that have a living cellular neighbor in that generation. Make sure that each cell is listed only once!
Something not work with my deadNeighbors function, but I do not know what is wrong about it. Can anybody help me to fix deadNeighbors ?
type Coordinate = (Integer, Integer)
type Generation = [Coordinate]
single :: Generation
single = [ (42, 42) ]
row :: Generation
row = [ (10, 1), (10, 2), (10, 3) ]
Code:
neighbors :: Coordinate -> [Coordinate]
neighbors (x,y) = [(x-1,y-1), (x-1,y), (x-1,y+1), (x ,y-1), (x,y+1), (x+1,y-1), (x+1,y), (x+1,y+1)]
alive :: Generation -> Coordinate -> Bool
alive x y = elem y x
livingNeighbors :: Generation -> Coordinate -> Int
livingNeighbors a = length .filter (alive a) . neighbors
staysAlive :: Generation -> Coordinate -> Bool
staysAlive a b
| alive a b = livingNeighbors a b `elem` [2,3]
| otherwise = livingNeighbors a b `elem` [3]
Problems:
deadNeighbors :: Generation -> [Coordinate]
deadNeighbors (neighbors (x,y))
|(alive (x,y)) = Nothing
|otherwise = [] ++ (x,y)
Examples:
sort (deadNeighbors single) == sort [(41.41), (41.42), (41.43), (42.41), (42.43), (43.41), (43.42) , (43.43)]
sort (deadNeighbors row) == sort [(9.0), (9.1), (9.2), (10.0), (11.0), (11.1), (11.2) , (9.3), (11.3), (9.4), (10.4), (11.4)]
Let's go over deadNeighbors function line by line:
(the type signature looks fine)
deadNeighbors (neighbors (x,y))
This is a clause of the deadNeighbors function and seems to use pattern matching notation, however you use the function neighbors in the pattern match. That is not allowed. I don't really know what your intention is here, so I cannot suggest a way to fix this.
|(alive (x,y)) = Nothing
Here you are using a guard correctly, but the alive function requires the generation in addition to the coordinate. You should pass both as arguments here.
Also, you are returning Nothing which has type Maybe a for some a. But the signature of the deadNeighbors function indicates it should return a [Coordinate]. Perhaps you intended to write [] (the empty list)?
|otherwise = [] ++ (x,y)
Here you are using the ++ operator, which expects two lists as arguments, with an empty list [] as argument and a coordinate (x,y). The coordinate type is not a list, so it is not compatible with the ++ operator. Perhaps you intended (x,y) : [] or just [(x,y)] (which means the same thing but is slightly prettier)?
I'm working with relations and I want to find whether a relation is symmetric.
To find that a relation is symmetric, we need to find two tuples such that: [(a,b), (b,a)].
This is what I've got so far:
simmetry:: Eq a => [(a,a)] -> [a]
simmetry [] = []
simmetry (x:xs)
| (fst x `elem` map snd xs) && (snd x `elem` map fst xs) = fst x : (simmetry xs)
| otherwise = simmetry xs
What this function does is, it grabs a tuple x and checks that it finds its first element in another tuple as the second position, as well as checking that the second element is in another tuple as the first position.
However I'm missing out on the part where I have to check that the other tuple is the same one for both conditions. With my code, something like this: [(a,b),(b,c),(d,a)] would work.
P.D: simmetry returns [a] for testing purposes.
I'm out of ideas, any tips are highly appreciated!
What you want to check is: for every tuple (x,y) in the list, (y,x) should also be present. You can express that quite directly in Haskell:
isSymmetric :: Eq a => [(a,a)] -> Bool
isSymmetric l = all (\(x,y) -> (y,x)`elem`l) l
This is actually doing some redundant work because it always also goes over (x,y) itself, which your not really interested in, but it doesn't really matter. However it's a good exercise to design this in a way so it doesn't go over the element itself; for this it's helpful to use an auxiliary function
foci :: [a] -> [(a,[a])]
witht the behaviour
foci [p,q,r] ≡ [(p,[q,r]), (q,[p,r]), (r,[p,q])]
Then you left with an all over the foci of the input list, i.e.
isSymmetric = all _ . foci
With what #Rik Van Toor said:
simmetry:: Eq a => [(a,a)] -> [a]
simmetry [] = []
simmetry (x:xs)
| (snd x, fst x) `elem` xs = fst x : (simmetry xs)
| otherwise = simmetry xs
Here is the problem I want to solve:
The two lists [1,2,3,4] [2,2,3,4] is to become [2,3,4] , because elements at index position zero are not equal. So elements are compared for equality with respect to their index. You can assume equal length lists as input.
So I created a function that solved this with recursion:
oneHelper :: (Eq t) => [t] -> [t] -> [t]
oneHelper [] [] = []
oneHelper (x:xs) (y:ys) = if x == y then [x] ++ oneHelper xs ys
else oneHelper xs ys
Then I tried to solve it with list comprehension like this:
test a b = [x | x <- a, y <- b, x == y]
which just gives me [2,2,3,4] with example input above used. I feel like there is a neat way of solving this with a list comprehension, or just a more neat way than the solution I came up with in general, but I am struggling to reach that solution. Does anyone see something better than what I did for the problem?
Thanks
If you use the two generators, you will iterate over all possible combinations of the elements in the first list (a) and second list (b).
You probably want to use zip :: [a] -> [b] -> [(a, b)] here where we iterate over the two lists concurrently:
test a b = [x | (x, y) <- zip a b, x == y]
I'm really struggling with Haskell atm.
It took me almost 6 hours to write a function that does what I want. Unfortunately I'm not satisfied with the look of it.
Could someone please give me any hints how to rewrite it?
get_connected_area :: Eq generic_type => [[generic_type]] -> (Int, Int) -> [(Int,Int)] -> generic_type -> [(Int,Int)]
get_connected_area habitat point area nullValue
| elem point area = area
| not ((fst point) >= 0) = area
| not ((snd point) >= 0) = area
| not ((fst point) < (length habitat)) = area
| not ((snd point) < (length (habitat!!0))) = area
| (((habitat!!(fst point))!!(snd point))) == nullValue = area
| otherwise =
let new_area = point : area
in
get_connected_area habitat (fst point+1, snd point) (
get_connected_area habitat (fst point-1, snd point) (
get_connected_area habitat (fst point, snd point+1) (
get_connected_area habitat (fst point, snd point-1) new_area nullValue
) nullValue
) nullValue
) nullValue
The function get's a [[generic_type]] (representing a landscape-map) and searches the fully connected area around a point that isn't equal to the given nullValue.
Eg.:
If the function gets called like this:
get_connected_area [[0,1,0],[1,1,1],[0,1,0],[1,0,0]] (1,1) [] 0
That literally means
0 1 0
1 1 1
0 1 0
1 0 0
Represents a map (like google maps). Start from the point (coordinates) (1,1) I want to get all coordinates of the elements that form a connected area with the given point.
The result therefore should be:
0 1 0
1 1 1
0 1 0
1 0 0
And the corresponting return value (list of coordinates of bold 1s):
[(2,1),(0,1),(1,2),(1,0),(1,1)]
One small change is that you can use pattern matching for the variable point. This means you can use (x, y) instead of point in the function declaration:
get_connected_area habitat (x, y) area nullValue = ...
Now everywhere you have fst point, just put x, and everywhere you have snd point, put y.
Another modification is to use more variables for subexpressions. This can help with the nested recursive calls. For example, make a variable for the inner-most nested call:
....
where foo = get_connected_area habitat (x, y-1) new_area nullValue
Now just put foo instead of the call. This technique can now be repeated for the "new" inner-most call. (Note that you should pick a more descriptive name than foo. Maybe down?)
Note that not (x >= y) is the same as x < y. Use this to simplify all of the conditions. Since these conditions test if a point is inside a bounding rectangle, most of this logic can be factored to a function isIn :: (Int, Int) -> (Int, Int) -> (Int, Int) -> Bool which will make get_connected_area more readable.
This would be my first quick pass through the function, and sort of the minimum that might pass a code review (just in terms of style):
getConnectedArea :: Eq a => [[a]] -> a -> (Int, Int) -> [(Int,Int)] -> [(Int,Int)]
getConnectedArea habitat nullValue = go where
go point#(x,y) area
| elem point area = area
| x < 0 = area
| y < 0 = area
| x >= length habitat = area
| y >= length (habitat!!0) = area
| ((habitat!!x)!!y) == nullValue = area
| otherwise =
foldr go (point : area)
[ (x+1, y), (x-1, y), (x, y+1), (x, y-1) ]
We bind habitat and nullValue once at the top level (clarifying what the recursive work is doing), remove indirection in the predicates, use camel-case (underdashes obscure where function application is happening), replace generic_type with a (using a noisy variable here actually has the opposite effect from the one you intended; I end up trying to figure out what special semantics you're trying to call out when the interesting thing is that the type doesn't matter (so long as it can be compared for equality)).
At this point there are lots of things we can do:
pretend we're writing real code and worry about asymptotics of treating lists as arrays (!!, and length) and sets (elem), and use proper array and set data structures instead
move your bounds checking (and possible null value checking) into a new lookup function (the goal being to have only a single ... = area clause if possible
consider improvements to the algorithm: can we avoid recursively checking the cell we just came from algorithmically? can we avoid passing area entirely (making our search nicely lazy/"productive")?
Here is my take:
import qualified Data.Set as Set
type Point = (Int, Int)
getConnectedArea :: (Point -> Bool) -> Point -> Set.Set Point
getConnectedArea habitat = \p -> worker p Set.empty
-- \p is to the right of = to keep it out of the scope of the where clause
where
worker p seen
| p `Set.member` seen = seen
| habitat p = foldr worker (Set.insert p seen) (neighbors p)
| otherwise = seen
neighbors (x,y) = [(x-1,y), (x+1,y), (x,y-1), (x,y+1)]
What I've done
foldr over the neighbors, as some commenters suggested.
Since the order of points doesn't matter, I use a Set instead of a list, so it's semantically a better fit and faster to boot.
Named some helpful intermediate abstractions such as Point and neighbors.
A better data structure for the habitat would also be good, since lists are linear time to access, maybe a 2D Data.Array—but as far as this function cares, all you need is an indexing function Point -> Bool (out of bounds and null value are treated the same), so I've replaced the data structure parameter with the indexing function itself (this is a common transformation in FP).
We can see that it would also be possible to abstract away the neighbors function and then we would arrive at a very general graph traversal method
traverseGraph :: (Ord a) => (a -> [a]) -> a -> Set.Set a
in terms of which you could write getConnectedArea. I recommend doing this for educational purposes—left as an exercise.
EDIT
Here's an example of how to call the function in terms of (almost) your old function:
import Control.Monad ((<=<))
-- A couple helpers for indexing lists.
index :: Int -> [a] -> Maybe a
index _ [] = Nothing
index 0 (x:_) = x
index n (_:xs) = index (n-1) xs
index2 :: (Int,Int) -> [[a]] -> Maybe a
index2 (x,y) = index x <=< index y
-- index2 uses Maybe's monadic structure, and I think it's quite pretty.
-- But if you're not ready for that, you might prefer
index2' (x,y) xss
| Just xs <- index y xss = index x xs
| otherwise = Nothing
getConnectedArea' :: (Eq a) => [[a]] -> Point -> a -> [a]
getConnectedArea' habitat point nullValue = Set.toList $ getConnectedArea nonnull point
where
nonnull :: Point -> Bool
nonnull p = case index2 p habitat of
Nothing -> False
Just x -> x /= nullValue
OK i will try to simplify your code. However there are already good answers and that's why i will tackle this with a slightly more conceptual approach.
I believe you could chose better data types. For instance Data.Matrix seems to provide an ideal data type in the place of your [[generic_type]] type. Also for coordinates i wouldn't chose a tuple type since tuple type is there to pack different types. It's functor and monad instances are not very helpful when it is chosen as a coordinate system. Yet since it seems Data.Matrix is just happy with tuples as coordinates i will keep them.
OK your rephrased code is as follows;
import Data.Matrix
gca :: Matrix Int -> (Int, Int) -> Int -> [(Int,Int)]
gca fld crd nil = let nbs = [id, subtract 1, (+1)] >>= \f -> [id, subtract 1, (+1)]
>>= \g -> return (f,g)
>>= \(f,g) -> return ((f . fst) crd, (g . snd) crd)
in filter (\(x,y) -> fld ! (x,y) /= nil) nbs
*Main> gca (fromLists [[0,1,0],[1,1,1],[0,1,0],[1,0,0]]) (2,2) 0
[(2,2),(2,1),(2,3),(1,2),(3,2)]
The first thing to note is, the Matrix data type is index 1 based. So we have our center point at (2,2).
The second is... we have a three element list of functions defined as [id, subtract 1, (+1)]. The contained functions are all Num a => a -> a type and i need them to define the surrounding pixels of the given coordinate including the given coordinate. So we have a line just like if we did;
[1,2,3] >>= \x -> [1,2,3] >>= \y -> return [x,y] would result [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]] which, in our case, would yield a 2 combinations of all functions in the place of the numbers 1,2 and 3.
Which then we apply to our given coordinate one by one with a cascading instruction
>>= \[f,g] -> return ((f . fst) crd, (g . snd) crd)
which yields all neighboring coordinates.
Then its nothing more than filtering the neighboring filters by checking if they are not equal to the nil value within out matrix.
I thought I was smooth sailing in my Haskell studies, until...
I have a [[Int]]
tiles = [[1,0,0]
,[0,1,0]
,[0,1,0]
]
and a data type:
data Coord = Coord
{ x :: Int
, y :: Int
} deriving (Eq)
Based on the input tiles, I've been trying to output a [Coord], such that a Coord is only generated when the value of tiles is 1, and the Coord will store it's position in the 2d list:
blackBox :: [[Int]] -> [Coord]
blackBox tiles = <magic>
-- given the above example I would expect:
-- [(Coord 0 0),(Coord 1 1),(Coord 1 2)]
I have tried things like first converting [[Int]] to a [Int], via:
foldTiles :: [[Int]] -> [Int]
foldTiles tiles = foldr (++) [] tiles
but after that I'm not really sure how to pass the indices along. I suppose if I could map over the "folded tiles", outputting a tuple (value, index), I could easily figure out the rest.
update In case anyone's interested, I got it working and here is a demo of it (with source code and link to GitHub)! I will have to take more time to understand each of the answers as this is my first time programming a game using FP. Thanks a lot!
http://kennycason.com/posts/2013-10-10-haskell-sdl-gameboy-boxxle.html
This is a place where list comprehensions shine.
blackBox tiles =
[Coord x y -- generate a Coord pair
| (y, row) <- enumerate tiles -- for each row with its coordinate
, (x, tile) <- enumerate row -- for each tile in the row (with coordinate)
, tile == 1] -- if the tile is 1
Or you could go for the equivalent do notation (since list is a monad), which requires importing Control.Monad (for guard.)
blackBox tiles = do
(y, row) <- enumerate tiles -- for each row with its coordinate
(x, tile) <- enumerate row -- for each tile in the row (with coordinate)
guard (tile == 1) -- as long as the tile is 1
return (Coord x y) -- return a coord pair
To aid with understanding, this latter function works like the following Python function.
def black_box(tiles):
for y, row in enumerate(tiles):
for x, tile in enumerate(row):
if tile == 1:
yield Coord(x, y)
do notation for the list monad is incredibly handy for processing lists, I think, so it's worth wrapping your head around!
In both of these examples I have used the definition
enumerate = zip [0..]
Here's a simple solution (not guarantee that it's viable for tiles of size 10000x10000, that's something for you to check ;)
The approach is, as usual in Haskell, a top-down development. You think: what should blackBox do? For every row of tiles it should collect the Coords of the tiles with 1 for that row, and concatenate them.
This gives you another function, blackBoxRow, for rows only. What should it do? Remove zeros from the row, and wrap the rest in Coords, so there's filter and then map. Also you want to keep the row and column numbers, so you map tiles joined with their respective coordinates.
This gives you:
tiles :: [[Int]]
tiles = [[1,0,0]
,[0,1,0]
,[0,1,0]
]
data Coord = Coord {
x :: Int
,y :: Int
} deriving (Eq, Show)
blackBox :: [[Int]] -> [Coord]
blackBox tiles2d = concat (map blackBoxRow (zip [0..] tiles2d))
blackBoxRow :: (Int, [Int]) -> [Coord]
blackBoxRow (row, tiles1d) = map toCoord $ filter pickOnes (zip [0..] tiles1d) where
pickOnes (_, value) = value == 1
toCoord (col, _) = Coord {x=col, y=row}
main = print $ blackBox tiles
Results in:
~> runhaskell t.hs
[Coord {x = 0, y = 0},Coord {x = 1, y = 1},Coord {x = 1, y = 2}]
The way I see it, you could put your 2D list through a series of transformations. The first one we'll need is one that can replace the 1 in your list with something more useful, such as its row:
assignRow :: Int -> [Int] -> [Int]
assignRow n xs = map (\x -> if x == 1 then n else x) xs
We can now use zipWith and [1..] to perform the first step:
assignRows :: [[Int]] -> [[Int]]
assignRows matrix = zipWith assignRow [1..] matrix
What's handy about this is that it'll work even if the matrix isn't square, and it terminates as soon as the matrix does.
Next we need to assign the column number, and here I'll do a few steps at once. This makes the tuples of the coordinates, but there are invalid ones where r == 0 (this is why I used [1..], otherwise, you'll lose the first row), so we filter them out. Next, we uncurry Coord to make a function that takes a tuple instead, and then we use flip on it, then map this thing over the list of tuples.
assignCol :: [Int] -> [Coord]
assignCol xs = map (uncurry (flip Coord)) $ filter (\(c, r) -> r /= 0) $ zip [1..] xs
And we can build our assignCols:
assignCols :: [[Int]] -> [Coord]
assignCols matrix = concatMap assignCol matrix
which allows us to build the final function
assignCoords :: [[Int]] -> [Coord]
assignCoords = assignCols . assignRows
You could compress this quite a bit with some eta reduction, too.
If you want 0-indexed coordinates, I'll leave you to modify this solution to do so.
Quick and dirty solution:
import Data.Maybe (mapMaybe)
data Coord = Coord {
x :: Int
,y :: Int
} deriving (Eq, Show)
blackBox :: [[Int]] -> [Coord]
blackBox = concatMap (\(y, xks) -> mapMaybe (toMaybeCoord y) xks)
. zip [0..] . map (zip [0..])
where
toMaybeCoord :: Int -> (Int, Int) -> Maybe Coord
toMaybeCoord y (x, k) = if k == 1
then Just (Coord x y)
else Nothing
The zips pair the the tile values (which I am referring to as k) with the x and y coordinates (we are dealing with lists, so we have to add the indices if we need them). mapMaybe is convenient so that we can map (in order to construct the Coords) and filter (to remove the zero tiles) in a single step. concatMap also does two things here: it maps a function (the anonymous function within the parentheses) generating a list of lists and then flattens it. Be sure to check the types of the intermediate functions and results to get a clearer picture of the transformations.
Here it is, using list comprehensions.
blackBox :: [[Integer]] -> [Coord]
blackBox ts = [Coord x y | (t,y) <- zip ts [0..], (e,x) <- zip t [0..], e == 1]
As long as we're collecting answers, here's another:
blackBox :: [[Int]] -> [Coord]
blackBox ts = map (uncurry Coord) xsAndYs
where
xsAndYs = concat $ zipWith applyYs [0..] x1s
applyYs i = map (flip (,) i)
x1s = map (map fst . filter ((==1) . snd)) xs
xs = map (zip [0..]) ts
Explanation:
This assigns the x indexes within each row:
xs = map (zip [0..]) ts
Then I filter each row to keep only the elements with a 1, and then I drop the 1 (since it's no longer useful):
x1s = map (map fst . filter ((==1) . snd)) xs
Which results in something of type [[Int]], which are the rows with xs where 1s used to be. Then I map the ys within each row, flipping the pairs so I'm left with (x,y) instead of (y,x). As a final step, I flatten the rows into a single list, since I don't need to keep them separate anymore:
xsAndYs = concat $ zipWith applyYs [0..] x1s
applyYs i = map (flip (,) i)
Finally I convert each element by mapping Coord over it. uncurry is necessary because Coord doesn't take a tuple as argument.