1

I was doing a python challenge and this one stumped me. This is the input matrix (numpy format):

# [[1, 7, 2, 2, 1],
#  [7, 7, 9, 3, 2],
#  [2, 9, 4, 4, 2],
#  [2, 3, 4, 3, 2],
#  [1, 2, 2, 7, 1]]

and the function would output this matrix

# [[False, True, False, False, False],
#  [True, False, True, False, False],
#  [False, True, False, True, False],
#  [False, False, False, False, False],
#  [False, False, False, True, False]]

And you can see the value will be 'true' if any (up/down/left/right) neighbor is 2 smaller than itself. We've been learning numpy, but this doesn't feel like it's too much of a numpy thing).

I tried to do simple if comparison=true checks, but I kept stumbling into out-of-index errors and I couldnt find any way to circumvent/ignore those.

Thanks in advance.


This is the essence of what I've tried so far. I've simplified the task here to simply check the first row horizontally. If I could get this to work, I would extend it to check the next row horizontally until the end, and then I would do the same thing but vertically.

import numpy as np

ex=np.array([[7, 2, 3, 4, 3, 4, 7]])

def count_peaks(A):
    
    matrixHeight=A.shape[0]
    matrixWidth=A.shape[1]
    
    peakTable=np.zeros(shape=(matrixHeight,matrixWidth))
    
    
    for i in range(matrixWidth):
        if A[i]-A[i+1]>=2 or A[i]-A[i-1]>=2:
            peakTable[0,i]=1

    return peakTable

... which of course outputs:

IndexError: index 1 is out of bounds for axis 0 with size 1

as I'm trying to find the value of A[-1] which doesn't exist.

2 Answers2

2

You are using numpy arrays, so don't loop, use vectorial code:

import numpy as np

# get shape
x,y = a.shape

# generate row/col of infinites
col = np.full([x, 1], np.inf)
row = np.full([1, y], np.inf)

# shift left/right/up/down
# and compute difference from initial array
left = a - np.c_[col, a[:,:-1]]
right = a - np.c_[a[:,1:], col]
up = a - np.r_[row, a[:-1,:]]
down = a -np.r_[a[1:,:], row]

# get max of each shift and compare to threshold
peak_table = np.maximum.reduce([left,right,up,down])>=2

# NB. if we wanted to use a maximum threshold, we would use
# `np.minimum` instead and initialize the shifts with `-np.inf`

output:

array([[False,  True, False, False, False],
       [ True, False,  True, False, False],
       [False,  True, False,  True, False],
       [False, False,  True, False, False],
       [False, False, False,  True, False]])

input:

import numpy as np
a = np.array([[1, 7, 2, 2, 1],
              [7, 7, 9, 3, 2],
              [2, 9, 4, 4, 2],
              [2, 3, 4, 3, 2],
              [1, 2, 2, 7, 1]])
mozway
  • 194,879
  • 13
  • 39
  • 75
  • I am getting TypeError: list indices must be integers or slices, not tuple when I hit the line starting with the left variable, the variable a is just the numpy array of arrays right? – Richard K Yu Jan 15 '22 at 20:09
  • 1
    @RichardKYu I added the input **array** (not list) for clarity, please check with this one. I checked again and it works fine for me. – mozway Jan 15 '22 at 20:12
  • This was really nice. So you shifted in the four directions for each value, and then the maximum of the four differences produced an array of ints we can convert into the peak table. Is the "inf" the way of representing that the comparison in one of the directions "doesn't count" for certain entries, because when we apply max function in reduce, the -inf will always get ignored? – Richard K Yu Jan 15 '22 at 20:23
  • You got it all ;) – mozway Jan 15 '22 at 20:24
0

If you don't mind me not using numpy to get the solution, but converting to numpy at the end, here is my attempt:

import numpy as np

def check_neighbors(mdarray,i,j):
    neighbors = (-1, 0), (1, 0), (0, -1), (0, 1)

    for neighbor in neighbors:
        try:
            if mdarray[i][j]-mdarray[i+neighbor[0]][j+neighbor[1]]>=2:
                return True
        except IndexError:
            pass

    return False


mdarray= [[1, 7, 2, 2, 1],
        [7, 7, 9, 3, 2],
        [2, 9, 4, 4, 2],
        [2, 3, 4, 3, 2],
        [1, 2, 2, 7, 1]]

peak_matrix =[]

for i in range(len(mdarray)):
    row = []
    for j in range(len(mdarray[i])):
        #print(check_neighbors(mdarray,i,j))
        row.append(check_neighbors(mdarray,i,j))
    peak_matrix.append(row)


y=np.array([np.array(xi) for xi in peak_matrix])
print(y)

I use the try-except block to avoid errors when the index goes out of bounds.

Note: Row 4 Column 3 (starting counts at 1) of my output seems to differ from yours. I think that the 4 and 2 difference in the neighbors should make this entry true?

Output:

[[False  True False False False]
 [ True False  True False False]
 [False  True False  True False]
 [False False  True False False]
 [False False False  True False]]

Edit: changed from bare except to IndexError as Neither suggests in the comments. pass and continue doesn't make a difference in this case but yes.

Richard K Yu
  • 2,152
  • 3
  • 8
  • 21
  • Ooh, that's a cool way to do it! Also, I had no idea you could do except: continue. That's exactly the kind of hack I needed to make my code work lol. Thanks! – joachimnogra Jan 15 '22 at 19:49
  • @joachimnogra Glad I could help! If this has helped you solve the problem be sure to mark it complete with the check. – Richard K Yu Jan 15 '22 at 19:51
  • @joachimnogra FYI, [bare except](https://stackoverflow.com/questions/54948548/what-is-wrong-with-using-a-bare-except) is considered bad. Also, `continue` there can be replaced by `pass` because it will continue the loop after that as it's the last statement anyway... –  Jan 15 '22 at 20:43