1

I'm trying to implement a function to match a specific pattern in Haskell inside a 2D grid. For example, if my grid is defined as follows:

grid = [[0,0,0,0],
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0]] 

and the pattern I am looking for is:

pattern = [[1, 1], 
           [0, 0]]

I would like it to return the index of the top left element in the grid. In this case, (2,1). My function has the following type:

matches2d :: Eq a 
          => [[a]]          -- ^ The 2D grid to match over 
          -> [[a]]          -- ^ The 2D pattern to match
          -> [(Int, Int)]   -- ^ Top left corner of sections matching the pattern

I am not exactly sure how to approach this problem. The method I am trying to use here is a sliding window of the pattern over the grid, but I've been trying to implement something like that for a bit now. This is what I have so far, but I think I'm missing some of the logic behind it:

matches2d :: Eq a => [[a]] -> [[a]] -> [(Int, Int)] 
matches2d _ [] = [] 
matches2d f (x:xs) = matcher x pattern 
  where matcher x y (x:xs) = sort x == sort y

Thanks in advance for you help.

MikaelF
  • 3,518
  • 4
  • 20
  • 33
MoodMojo
  • 47
  • 5
  • How would you approach this for a 1d list with a 1d pattern? – Willem Van Onsem Mar 11 '20 at 16:16
  • You should post your attempt, as it may be closer to working than you think. – chepner Mar 11 '20 at 16:16
  • I found this link where they made a 1d window-slider https://stackoverflow.com/questions/27726739/implementing-an-efficient-sliding-window-algorithm-in-haskell Check it out. Might solve your problem. – MoodMojo Mar 11 '20 at 16:18
  • @chepner It didn't even run for me so I wouldn't put my odds on that. What I tried to do is make a local function for the sliding window, but I couldn't define it for more than 1 row. – MoodMojo Mar 11 '20 at 16:21
  • 2
    There are lots of tiny problems that can render a function unusable. Please post your attempt at the definition so we have something specific to worth with. Right now, your question is too broad. – chepner Mar 11 '20 at 16:24
  • Sorry but this is totally unclear. For instance why exactly you have the return type `[(Int, Int)]`? Howmany coordinates do you expect..? Also for your `[1, 1], [0, 0]` pattern it looks to me like you have only one solution which is `[[0,0,0,0],[0,1,1,0],[0,` **1,1** `,0],[0,` **0,0** `,0]]`. – Redu Mar 11 '20 at 16:39
  • @Redu I have the return type as [Int, Int] for the case of multiple matches in the grid. And yes, in the example I gave the only solution would be at (2,1) coordinate(the top left in the grid). If the grid was `[[1,0,1,0,1],[0,1,0,1,0],[1,0,1,0,1],[0,1,0,1,0],[1,0,1,0,1]]`, for example, and the pattern `[[0,1,0],[1,0,1],[0,1,0]]` Then the output should be all the coordinates that match the pattern. In such a case, `[(0,1),(1,0),(1,2),(2,1)]` I hope this clarifies it. – MoodMojo Mar 11 '20 at 16:49
  • @chepner this is what I managed to do thus far. I don't, myself, think it would work because I think I'm missing the logic behind this. `matches2d :: Eq a => [[a]] -> [[a]] -> [(Int, Int)] matches2d _ [] = [] matches2d f (x:xs) = matcher x pattern where matcher x y (x:xs) = sort x == sort y` – MoodMojo Mar 11 '20 at 16:50
  • @MoodMojo: why do you use `sort` here? – Willem Van Onsem Mar 11 '20 at 16:52
  • @WillemVanOnsem Now that you mention it. – MoodMojo Mar 11 '20 at 16:56
  • solve it with recursion. – Will Ness Mar 11 '20 at 20:38
  • @WillNess I have tried that. I am still a beginner in Haskell so that's why I am struggling with this. Online resources are of no help either. – MoodMojo Mar 11 '20 at 20:51
  • you mean [this code](https://stackoverflow.com/questions/60640322/haskell-2d-sliding-window-pattern-matching?noredirect=1#comment107285652_60640322)? is it all you wrote, or is there some more? – Will Ness Mar 11 '20 at 21:12
  • 1
    show us your attempt at 1D matching please. you say you could do it for one row. show it. – Will Ness Mar 11 '20 at 22:57
  • 1
    you still haven't shown us any meaningful piece of code. please, show us your 1D code which you've implied you could do. the reason I ask this is because otherwise we can't know what's stopping you from completing the task. We can post some code for you, but if it starts from way above your current level, what will you understand there? and even if you do, what will you learn from that? what if you see something like `zipWith (zipWith (==) ...) ...`, will such code be clear to you? is it hint enough in itself? do respond, otherwise, we might conclude you've lost interest in this topic. – Will Ness Mar 12 '20 at 09:33
  • 2
    your question can yet be reopened but my *guess* is it is much more likely to happen if we get some feedback from you. – Will Ness Mar 12 '20 at 09:51
  • @WillNess it is ok, will. Thanks for your consideration but I pulled an all-nighter and figured it out. – MoodMojo Mar 13 '20 at 06:04
  • 1
    Well, since you've figured it out, you can now post your own answer and later accept it! you could get some reputation on it, too. It is reopened now, so you be able to do that. You don't have to post the whole code, in case it was for an assignment; some highlights and explanations will do, I think. Cheers. – Will Ness Mar 13 '20 at 08:51

2 Answers2

0

So the solution for this took me some time. I needed to define another helper function and a few others to get it to work. To boil it down there is a slicer function: which takes a full grid and two tuples then extracts the subgrid with the rows in the range of the first tuple and the columns of the second tuple. The definition for it is as follows:

slicer :: [[a]] -> (Int, Int) -> (Int, Int) -> [[a]]
slicer [] _ _ = []
slicer a (x1,x2) (y1,y2) =form (head ( [[index a (i,j)|i<-[x1..x2-1],j<-[y1..y2-1]]])) (x2-x1,y2-y1)

I also needed a function to return the value at a given index in the grid, index:

index :: [[a]] -> (Int, Int) -> a
index (b:bb) (0,0) = head b
index (c:cc) (0,y) = index [(tail c)] (0,y-1)
index (d:dd) (x,y) = index dd (x-1,y)

Next thing was to define that helper function. That took most of the work of the function. It helped to "nest" the operation over the entire grid. Its entire function is to slide the window with the fixed size all over the grid and find a match. Once it does, it gives the top left-most coordinates of that matching window. The definition for it is this:

helper :: Eq a => [[a]] -> [[a]] -> (Int, Int, Int) -> [(Int, Int)]
helper x _ (l, m, n) | (l > length x) = []
helper x y (l, m, n) | (n > length (concat (take 1 x))) = helper x y (l+1, 0, (length (concat (take 1 y))))
helper x y (l, m, n) | (slicer x (l, (length y) + l) (m, n) == y) = [(l, m)] ++ helper x y (l, (m+1), (n+1))
helper x y (l, m, n) | (slicer x (l, (length y) + l) (m, n) /= y) = [] ++ helper x y (l, (m+1), (n+1))

Finally, after all those sub-functions came time to actually make the function I was trying to make from the beginning. Ironically enough, it was the simplest of them as it just takes the given pattern over the length of the grid.

matcher :: Eq a => [[a]] -> [[a]] -> [(Int, Int)]
matcher x y = helper x y (0, 0, (length (concat (take 1 y))))
MoodMojo
  • 47
  • 5
  • what is `form` used in `slicer`? – Will Ness Mar 19 '20 at 13:19
  • Forgot to include that. It's another function just for actually making a grid out of the lists I provide. Just takes a list and a tuple that is (height, width) and returns a grid in that shape. – MoodMojo Mar 20 '20 at 12:21
0

Solve it with recursion.

Here's some iterative development. In the following, you will need to have an understanding of list comprehensions and the zipWith function, first. It is the simplest function which solves the equation

zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys

or, if you're familiar with zip,

zipWith f xs ys = [f x y | (x,y) <- zip xs ys]

Also,

and (x:xs) = x && and xs
           =  all (\x -> x == True) xs
           =  all (\x -> x) xs
           =  all id xs

With that,

1.

matches2d_1 :: Eq a => [[a]] -> [[a]] -> Bool
matches2d_1 pattern xss = 
    and (map and (zipWith (zipWith (==)) pattern xss))     -- does it match?
    ||  matches2d_1 pattern (drop 1 xss)            -- or start at next row
    ||  matches2d_1 pattern (map (drop 1) xss)      -- or start at next column

This is wrong in edge cases, but gives a general idea. The core of it is the and (zipWith (\a b -> and (zipWith (==) a b)) pattern xss) bit.

Note that it only gives us the indication of the first successful result, but we want all of them, so, a list of them. Hence,

2.

matches2d_2 :: Eq a => [[a]] -> [[a]] -> [Bool]
matches2d_2 pattern xss    | shorter xss pattern       = []  -- not enough rows
matches2d_2 pattern (xs:_) | shorter xs (head pattern) = []  -- not enough columns
matches2d_2 pattern xss = 
    [True | and (map and (zipWith (zipWith (==)) pattern xss))]  -- this match
    ++  matches2d_2 pattern (drop 1 xss)            -- or other
    ++  matches2d_2 pattern (map (drop 1) xss)      --      ones...
shorter a b = -- length a < length b
       .... implement it better

This takes care of the edge cases, but instead of returning actual successful results it still just reports about each of them.

3.

matches2d :: Eq a => [[a]] -> [[a]] -> [(Int,Int)]
.....

Augmenting the above should give us the actual results that we want here.

4.

With working implementation following the specs, we can think about efficiency next. Does it do any redundant work, repeatedly? Can this be avoided?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Great news that you've followed up on it, and even finished it up! (many many newbie askers do not). Cheers, and you're welcome. :) – Will Ness Mar 28 '20 at 12:16