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.

Asked By: Vladouch

||

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.

Answered By: Chrysophylaxs
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.