Numpy ring arrays
Question:
I have been trying to learn numpy for a while and decided to code some complex exercises to verify that I understand everything, there is one that seems a bit odd to me.
The goal is to write a function only using numpy library and numpy indexing (no for/while loops no if statements), that will zero out all of the even rings in a matrix.
To start I tried to get a given ring, here is a function for that:
Here m is the matrix and l is the level of the ring, 0 being the outside one
def get_ring(m, l):
n = m.shape[0]
return np.concatenate((m[l:n-l, l:n-l], m[(l+1):n-l, -(l+1)], m[-(l+1), -(l+2):l:-1], m[n-(l+1):l:-1, l]))
I then went a bit further and wrote this function to zero out one ring using the np.where and some logic by oring the 4 sections that comprise a ring
Here is this function
def zero_ring(m, l):
n = m.shape[0]
m[l:n-l, l:n-l] = np.where(np.logical_or(np.logical_or(np.arange(n-l-l)[:, None] == 0, np.arange(n-l-l) == n-l-l-1),
np.logical_or(np.arange(n-l-l)[::-1][:, None] == 0, np.arange(n-l-l)[::-1] == n-l-l-1)), 0, m[l:n-l, l:n-l])
return m
To give you an example this it the output of for a 5×5 array
print(zero_ring(a, 1))
[[ 1 2 3 4 5]
[ 6 0 0 0 10]
[11 0 13 0 15]
[16 0 0 0 20]
[21 22 23 24 25]]
Now all that is left is to write the function to zero out all the even rings, but I just cant see how I would do that without using a for loop, because with it it would be immediate.
Answers:
Your code is a little bit complex for me to tell if you can extend it to multiple rings without any manual looping. I think it’ll be very tough to realize if you build each ring in a piecewise manner, but feel free to prove me wrong :).
That said, I do know an alternative, interesting way to build ring-like structures. Consider the array:
[3, 2, 1, 0, 1, 2, 3]
and the maximum between each pair of integers within that array:
3 2 1 0 1 2 3
/---------------------
3 | 3 3 3 3 3 3 3 |
2 | 3 2 2 2 2 2 3 |
1 | 3 2 1 1 1 2 3 |
0 | 3 2 1 0 1 2 3 |
1 | 3 2 1 1 1 2 3 |
2 | 3 2 2 2 2 2 3 |
3 | 3 3 3 3 3 3 3 |
---------------------/
The rings you’re looking for are exactly the even and odd elements in this array!
You might create such a starting 1D array of arbitrary size with np.abs
+ np.arange
:
def center(size):
return np.abs(np.arange(-size // 2 + 1, size // 2 + 1))
>>> center(7)
array([3, 2, 1, 0, 1, 2, 3])
and create the pairwise maximum between two such arrays either with broadcasting, or with np.maximum.outer
. To differentiate between even and odd rings, you can take modulus with 2.
def ring_mask(h, w):
rows = center(h)
cols = center(w)
return (np.maximum.outer(rows, cols) % 2).astype(bool)
Finally, use the mask in conjuction with np.where
:
def zero_rings(arr):
mask = ring_mask(*arr.shape)
return np.where(~mask, arr, 0)
import numpy as np
arr = np.arange(1, 82).reshape(9, 9)
out = zero_rings(arr)
out:
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 0, 0, 0, 0, 0, 0, 0, 18],
[19, 0, 21, 22, 23, 24, 25, 0, 27],
[28, 0, 30, 0, 0, 0, 34, 0, 36],
[37, 0, 39, 0, 41, 0, 43, 0, 45],
[46, 0, 48, 0, 0, 0, 52, 0, 54],
[55, 0, 57, 58, 59, 60, 61, 0, 63],
[64, 0, 0, 0, 0, 0, 0, 0, 72],
[73, 74, 75, 76, 77, 78, 79, 80, 81]])
PS: you may or may not need invert the mask (or add 1 before % 2
) depending on which rings you consider to be even and which to be odd.
I have been trying to learn numpy for a while and decided to code some complex exercises to verify that I understand everything, there is one that seems a bit odd to me.
The goal is to write a function only using numpy library and numpy indexing (no for/while loops no if statements), that will zero out all of the even rings in a matrix.
To start I tried to get a given ring, here is a function for that:
Here m is the matrix and l is the level of the ring, 0 being the outside one
def get_ring(m, l):
n = m.shape[0]
return np.concatenate((m[l:n-l, l:n-l], m[(l+1):n-l, -(l+1)], m[-(l+1), -(l+2):l:-1], m[n-(l+1):l:-1, l]))
I then went a bit further and wrote this function to zero out one ring using the np.where and some logic by oring the 4 sections that comprise a ring
Here is this function
def zero_ring(m, l):
n = m.shape[0]
m[l:n-l, l:n-l] = np.where(np.logical_or(np.logical_or(np.arange(n-l-l)[:, None] == 0, np.arange(n-l-l) == n-l-l-1),
np.logical_or(np.arange(n-l-l)[::-1][:, None] == 0, np.arange(n-l-l)[::-1] == n-l-l-1)), 0, m[l:n-l, l:n-l])
return m
To give you an example this it the output of for a 5×5 array
print(zero_ring(a, 1))
[[ 1 2 3 4 5]
[ 6 0 0 0 10]
[11 0 13 0 15]
[16 0 0 0 20]
[21 22 23 24 25]]
Now all that is left is to write the function to zero out all the even rings, but I just cant see how I would do that without using a for loop, because with it it would be immediate.
Your code is a little bit complex for me to tell if you can extend it to multiple rings without any manual looping. I think it’ll be very tough to realize if you build each ring in a piecewise manner, but feel free to prove me wrong :).
That said, I do know an alternative, interesting way to build ring-like structures. Consider the array:
[3, 2, 1, 0, 1, 2, 3]
and the maximum between each pair of integers within that array:
3 2 1 0 1 2 3
/---------------------
3 | 3 3 3 3 3 3 3 |
2 | 3 2 2 2 2 2 3 |
1 | 3 2 1 1 1 2 3 |
0 | 3 2 1 0 1 2 3 |
1 | 3 2 1 1 1 2 3 |
2 | 3 2 2 2 2 2 3 |
3 | 3 3 3 3 3 3 3 |
---------------------/
The rings you’re looking for are exactly the even and odd elements in this array!
You might create such a starting 1D array of arbitrary size with np.abs
+ np.arange
:
def center(size):
return np.abs(np.arange(-size // 2 + 1, size // 2 + 1))
>>> center(7)
array([3, 2, 1, 0, 1, 2, 3])
and create the pairwise maximum between two such arrays either with broadcasting, or with np.maximum.outer
. To differentiate between even and odd rings, you can take modulus with 2.
def ring_mask(h, w):
rows = center(h)
cols = center(w)
return (np.maximum.outer(rows, cols) % 2).astype(bool)
Finally, use the mask in conjuction with np.where
:
def zero_rings(arr):
mask = ring_mask(*arr.shape)
return np.where(~mask, arr, 0)
import numpy as np
arr = np.arange(1, 82).reshape(9, 9)
out = zero_rings(arr)
out:
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 0, 0, 0, 0, 0, 0, 0, 18],
[19, 0, 21, 22, 23, 24, 25, 0, 27],
[28, 0, 30, 0, 0, 0, 34, 0, 36],
[37, 0, 39, 0, 41, 0, 43, 0, 45],
[46, 0, 48, 0, 0, 0, 52, 0, 54],
[55, 0, 57, 58, 59, 60, 61, 0, 63],
[64, 0, 0, 0, 0, 0, 0, 0, 72],
[73, 74, 75, 76, 77, 78, 79, 80, 81]])
PS: you may or may not need invert the mask (or add 1 before % 2
) depending on which rings you consider to be even and which to be odd.