determine the logic to do this
- A 1-d array with 6 items has the indices -
[0,1,2,3,4,5]
- A proposed shape is 2 rows and 3 columns -
M,N = 2,3
.
- for any item's index (
i
) its row and column are c,r = divmod(i,M)
- The neighbor column,row indices will be
-
cplus,cminus = c + 1, c - 1
rplus, rminus = r + 1, r - 1
cplus,r
cminus,r
c,rplus
c,rminus
cplus,rplus
cplus,rminus
cminus,rplus
cminus,rminus
- those 2d indices need to be converted to 1d indices with
(col * M) + row
For example
[0,1,2,3,4,5]
M,N = 2,3
'''
0 2 4
1 3 5
'''
item 4's 2d index is c,r = divmod(4,M)
--> (2,0)
(col,row)
one of its neighbor's 2d index is c,rplus
--> (2,1)
that neighbor's 1d index is (2 * M) + 1
--> 5
after converting the neighbors' 2d indices to 1d you will need to check for and discard some that don't make sense.
- item 4 is in the top-right a corner and doesn't have some neighbors like
c,rminus
which would be (2,-1)
which does not make sense. Or cplus,r
... (3,0)
which also does not make sense.
Caveat - I did NOT try to thoroughly test this.
Here is a function that returns a callable.
import operator
def get_neighbors(index, shape=(M,N)):
'''Returns a callable.
(M,N) --> (number_of_rows, number_of_columns)
'''
M, N = shape
# 2d index
c, r = divmod(index, M)
print(f'2d index: {(c,r)}')
# neighbors
cplus, cminus = c + 1, c - 1
rplus, rminus = r + 1, r - 1
# dot product of (c,cplus,cminus) and (r,rplus,rminus)?
neighbors = [
(cminus, rminus),
(cminus, r),
(cminus, rplus),
(c, rplus),
(cplus, rplus),
(cplus, r),
(cplus, rminus),
(c, rminus),
]
# print(neighbors)
# validate/filter
neighbors = [
(col, row) for col, row in neighbors if (0 <= col < N) and (0 <= row < M)
]
# print(neighbors)
# 1d indices
one_d = [(col * M) + row for col,row in neighbors]
# print(one_d)
return operator.itemgetter(*one_d)
Try it out.
>>> a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't']
>>> M,N = 4,5 # nrows, ncols
'''
[['a' 'e' 'i' 'm' 'q']
['b' 'f' 'j' 'n' 'r']
['c' 'g' 'k' 'o' 's']
['d' 'h' 'l' 'p' 't']]
'''
>>> # i's neighbors
>>> q = get_neighbors(a.index('i'),(M,N))
2d index: (2, 0)
>>> q(a)
('e', 'f', 'j', 'n', 'm')
>>>
>>> # k's neighbors
>>> q = get_neighbors(a.index('k'),(M,N))
2d index: (2, 2)
>>> q(a)
('f', 'g', 'h', 'l', 'p', 'o', 'n', 'j')
>>>
>>> # q's neighbors
>>> q = get_neighbors(a.index('q'),(M,N))
2d index: (4, 0)
>>> q(a)
('m', 'n', 'r')
>>>
i
's neighbors for different shapes
>>> M,N = 5,4
>>> q = get_neighbors(a.index('i'),(M,N))
2d index: (1, 3)
>>> q(a)
('c', 'd', 'e', 'j', 'o', 'n', 'm', 'h')
>>> M,N = 10,2
>>> q = get_neighbors(a.index('i'),(M,N))
2d index: (0, 8)
>>> q(a)
('j', 't', 's', 'r', 'h')
>>> M,N = 2,10
>>> q = get_neighbors(a.index('i'),(M,N))
2d index: (4, 0)
>>> q(a)
('g', 'h', 'j', 'l', 'k')
>>>
There is a nice discussion in the Numpy docs about making/treating a 1d thing as a Nd thing - Internal memory layout of an ndarray
The way you depicted your 1d --> 2d transformation used a column major scheme. I'm used to thinking row-major - I wrote the function to accept/expect a (nrows,ncols) shape
argument but inside the function I kinda switched to column major processing. I was having to be careful so maybe that was a bad design.