Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions day14/2d-pattern-in-matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
########
# Code #
########

# pattern and matrix are both 2D arrays
# return true as soon as the pattern is found for a first time
def patternInMatrix(pattern, matrix):
rows, cols = len(matrix), len(matrix[0])
patternRows, patternCols = len(pattern), len(pattern[0])
for row in xrange(rows-patternRows):
for col in xrange(cols-patternCols):
found = False
for pRow in xrange(patternRows):
for pCol in xrange(patternCols):
if pattern[pRow][pCol] != matrix[row+pRow][col+pCol]:
found = False
break
else:
found = True
if found:
return True
return False

# return a list of all top-left starting indices
def indicesOfPatternInMatrix(pattern, matrix):
rows, cols = len(matrix), len(matrix[0])
patternRows, patternCols = len(pattern), len(pattern[0])

startingSpots = []
for row in xrange(rows-patternRows+1):
for col in xrange(cols-patternCols+1):
found = False
for pRow in xrange(patternRows):
for pCol in xrange(patternCols):
if pattern[pRow][pCol] != matrix[row+pRow][col+pCol]:
found = False
break
else:
found = True
if found:
startingSpots.append((row, col))
return startingSpots

#########
# Tests #
#########

def testPatterInMatrix():
assert patternInMatrix([[1]], [[0,0,0],[0,1,0],[0,0,0]])
assert not patternInMatrix([[2]], [[0,0,0],[0,1,0],[0,0,0]])

def testIndicesOfPatternInMatrix():
p0 = [[1]]

m0 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p0, m0) == [(1, 1)]

p1 = [[2]]

m1 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p1, m1) == []

p2 = [[0,1,0]]

m2 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p2, m2) == [(1, 0)]

p3 = [[0,0,0],
[0,1,0],
[0,0,0]]

m3 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p3, m3) == [(0,0)]

p4 = [[0]]

m4 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p4, m4) == [(0,0),(0,1),(0,2),
(1,0),(1,2),
(2,0),(2,1),(2,2)]

p4 = [[0, 0]]

m4 = [[0,0,0],
[0,1,0],
[0,0,0]]
assert indicesOfPatternInMatrix(p4, m4) == [(0,0),(0,1),
(2,0),(2,1)]

def main():
testPatterInMatrix()
testIndicesOfPatternInMatrix()

if __name__ == "__main__":
main()
86 changes: 86 additions & 0 deletions day14/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
Question of the day: https://www.careercup.com/question?id=5642834636963840

Given a 2D array of digits, try to find the occurence of a given 2D
pattern of digits. For example, consider the following 2D matrix:

```python
7283455864
6731158619
8988242643
3839505324
9509505813
3843845384
6473530293
7053106601
0834282956
4607924137
```

Assume we need to look for the following 2D pattern:

```python
950
384
353
```

Return a list of the top left indices of an occurence of the pattern in
the 2D matrix. If there are multiple occurences, return all of the
possible indices.

## Ideas

I'm going to make some assumptions to get myself started. I can explore
these assumptions more in the follow-up section later on.

1. We don't care about finding rotations of the 2D pattern inside the matrix.
2. We don't care about finding parts of the 2D patten inside the matrix
3. The dimensions of the 2D pattern are always less than or equal to the respective dimensions of the matrix.

A brute force solution would be to iterate through all possible locations
in the matrix and check if the 2D pattern matches starting at that
location. Let's define `width` and `height` to be the width and height
of the matrix. It would take `O(width*height)` time just to go through
all the possible starting locations. Let's define `pWidth` and `pHeight`
to be the width and height of the 2D pattern. So then if the pattern
matches, there would be an additional `O(pWidth*pHeight)` per location
with a possible start point, which excludes parts of the matrix. In the
worst case, the runtime to this brute force solution is
`O((width - pWidth + 1) * (height - pHeight + 1) * pWidth * pHeight)`. I do a
subtraction which is key, because in a matrix like this:

```python
11111
11111
11111
11111
11111
```

with a 2D pattern like this:

```python
1111
1111
1111
1111
```

there are only 4 possible starting locations: `(0, 0), (0, 1), (1,0), (1,1)`.

I don't think it's possible to do this problem and faster. So it's not
Copy link

@jjwon0 jjwon0 Apr 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I haven't seen or done this problem before, so this might be totally wrong.

This problem reminds me a lot of fast string matching, which means that there's probably a faster way to do it. In KMP and other linear string search algorithms, a key observation is that you might be able to skip steps. For example,

needle = 'abcd'
haystack = 'abcfffffabcdffffff'

When we compare by eye, we notice that the first three characters of needle and haystack match, but the fourth doesn't. Subsequently, we skip ahead when looking, because we know that the fourth character was an f, but needle[0] != 'f', so we can jump a few characters ahead. Specifically, we can start searching again from haystack[4:].

I suspect something similar can work in this problem, which would give it ultimately a runtime of O(nm), where n and m are the dimensions of the big matrix.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn uR RIGHT

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done thorough research yet, but it seems like this one also generalizes well to two-dimensional patern matching https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm

a very complex problem, now I just have to write the code.

## Code

[Python](./2D-pattern-in-matrix.py)

## Follow up

@jjwon0 made a great [observation](https://github.com/alberthu16/100-day-coding-challenge/pull/9#discussion_r113083672)
that the Knuth-Morris-Pratt linear time string matching algorithm may
be applied to 2D pattern matching to make this more efficient.

Read the [wikipedia](https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm)
article on the algorithm, and then read this guy's [blog post](http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/)
on what's going on with the preprocessing step.