How can I get a sublist with wraparound in Python
Question:
Simple 1D case
I would like to get a substring with wraparound.
str = "=Hello community of Python="
# ^^^^^ ^^^^^^^ I want this wrapped substring
str[-7]
> 'P'
str[5]
> 'o'
str[-7:5]
> ''
Why does this slice of a sequence starting at a negative index and ending in a positive one result in an empty string?
How would I get it to output "Python==Hell"?
Higher dimensional cases
In this simple case I could do some cutting and pasting, but in my actual application I want to get every sub-grid of size 2×2 of a bigger grid – with wraparound.
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
And I want to get all submatrices centered at some location (x, y)
, including '9 7; 3 1'
. Indexing with m[x-1:y+1]
doesn’t work for (x,y)=(0,0)
, nor does (x,y)=(1,0)
give 7 8; 1 2
3D example
m3d = np.array(list(range(27))).reshape((3,3,3))
>
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
m3d[-1:1,-1:1,-1:1]
# doesn't give [[[26, 24], [20, 18]], [8, 6], [2, 0]]]
If need be I could write some code which gets the various sub-matrices and glues them back together, but this approach might get quite cumbersome when I have to apply the same method to 3d arrays.
I was hoping there would be an easy solution. Maybe numpy can help out here?
Answers:
Simply combine the two halfs yourself:
>>> str[-7:]+str[:5]
'Python==Hell'
You could repeat your data enough so that you don’t need wraparound.
Substrings of length 3:
s = 'Python'
r = 3
s2 = s + s[:r-1]
for i in range(len(s)):
print(s2[i:i+r])
Output:
Pyt
yth
tho
hon
onP
nPy
Sub-matrices of size 2×2:
import numpy as np
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
r = 2
m2 = np.tile(m, (2, 2))
for i in range(3):
for j in range(3):
print(m2[i:i+r, j:j+r])
Output (Attempt This Online!):
[[1 2]
[4 5]]
[[2 3]
[5 6]]
[[3 1]
[6 4]]
[[4 5]
[7 8]]
[[5 6]
[8 9]]
[[6 4]
[9 7]]
[[7 8]
[1 2]]
[[8 9]
[2 3]]
[[9 7]
[3 1]]
For larger more-dimensional arrays, the simple np.tile
adds mmore than necessary. You really just need to increase the size by + r-1
in each dimension, not by * 2
. Like I did with the string. Not sure how to do that well with arrays. Plus I think you can also make your negative indexes work, so we just need someone to come along and do that.
Using Advanced indexing (see the section starting with "From a 4×3 array the corner elements should be selected using advanced indexing"):
import numpy as np
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
print(m[np.ix_(range(-1, 1), range(-1, 1))])
print(m[np.ix_(range(-2, 2), range(-2, 2))])
print(m[np.arange(-2, 2)[:, np.newaxis], range(-2, 2)])
Output (Attempt This Online!):
[[9 7]
[3 1]]
[[5 6 4 5]
[8 9 7 8]
[2 3 1 2]
[5 6 4 5]]
[[5 6 4 5]
[8 9 7 8]
[2 3 1 2]
[5 6 4 5]]
Going through all sub-matrices
Since you want to go through all sub-matrices, we can beforehand separately prepare the row ranges and the column ranges, and then use pairs of them to quickly index:
import numpy as np
A = np.mat('''1 2 3;
4 5 6;
7 8 9''')
m, n = A.shape
rowranges = [
(np.arange(i, i+2) % m)[:, np.newaxis]
for i in range(m)
]
colranges = [
np.arange(j, j+2) % n
for j in range(n)
]
for rowrange in rowranges:
for colrange in colranges:
print(A[rowrange, colrange])
Output (Attempt This Online!):
[[1 2]
[4 5]]
[[2 3]
[5 6]]
[[3 1]
[6 4]]
[[4 5]
[7 8]]
[[5 6]
[8 9]]
[[6 4]
[9 7]]
[[7 8]
[1 2]]
[[8 9]
[2 3]]
[[9 7]
[3 1]]
3D case
m3d = np.array(list(range(27))).reshape((3,3,3))
m3d[np.ix_(range(-1,1), range(-1,1), range(-1,1))]
Output:
array([[[26, 24],
[20, 18]],
[[ 8, 6],
[ 2, 0]]])
Simple 1D case
I would like to get a substring with wraparound.
str = "=Hello community of Python="
# ^^^^^ ^^^^^^^ I want this wrapped substring
str[-7]
> 'P'
str[5]
> 'o'
str[-7:5]
> ''
Why does this slice of a sequence starting at a negative index and ending in a positive one result in an empty string?
How would I get it to output "Python==Hell"?
Higher dimensional cases
In this simple case I could do some cutting and pasting, but in my actual application I want to get every sub-grid of size 2×2 of a bigger grid – with wraparound.
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
And I want to get all submatrices centered at some location (x, y)
, including '9 7; 3 1'
. Indexing with m[x-1:y+1]
doesn’t work for (x,y)=(0,0)
, nor does (x,y)=(1,0)
give 7 8; 1 2
3D example
m3d = np.array(list(range(27))).reshape((3,3,3))
>
array([[[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23],
[24, 25, 26]]])
m3d[-1:1,-1:1,-1:1]
# doesn't give [[[26, 24], [20, 18]], [8, 6], [2, 0]]]
If need be I could write some code which gets the various sub-matrices and glues them back together, but this approach might get quite cumbersome when I have to apply the same method to 3d arrays.
I was hoping there would be an easy solution. Maybe numpy can help out here?
Simply combine the two halfs yourself:
>>> str[-7:]+str[:5]
'Python==Hell'
You could repeat your data enough so that you don’t need wraparound.
Substrings of length 3:
s = 'Python'
r = 3
s2 = s + s[:r-1]
for i in range(len(s)):
print(s2[i:i+r])
Output:
Pyt
yth
tho
hon
onP
nPy
Sub-matrices of size 2×2:
import numpy as np
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
r = 2
m2 = np.tile(m, (2, 2))
for i in range(3):
for j in range(3):
print(m2[i:i+r, j:j+r])
Output (Attempt This Online!):
[[1 2]
[4 5]]
[[2 3]
[5 6]]
[[3 1]
[6 4]]
[[4 5]
[7 8]]
[[5 6]
[8 9]]
[[6 4]
[9 7]]
[[7 8]
[1 2]]
[[8 9]
[2 3]]
[[9 7]
[3 1]]
For larger more-dimensional arrays, the simple np.tile
adds mmore than necessary. You really just need to increase the size by + r-1
in each dimension, not by * 2
. Like I did with the string. Not sure how to do that well with arrays. Plus I think you can also make your negative indexes work, so we just need someone to come along and do that.
Using Advanced indexing (see the section starting with "From a 4×3 array the corner elements should be selected using advanced indexing"):
import numpy as np
m = np.mat('''1 2 3;
4 5 6;
7 8 9''')
print(m[np.ix_(range(-1, 1), range(-1, 1))])
print(m[np.ix_(range(-2, 2), range(-2, 2))])
print(m[np.arange(-2, 2)[:, np.newaxis], range(-2, 2)])
Output (Attempt This Online!):
[[9 7]
[3 1]]
[[5 6 4 5]
[8 9 7 8]
[2 3 1 2]
[5 6 4 5]]
[[5 6 4 5]
[8 9 7 8]
[2 3 1 2]
[5 6 4 5]]
Going through all sub-matrices
Since you want to go through all sub-matrices, we can beforehand separately prepare the row ranges and the column ranges, and then use pairs of them to quickly index:
import numpy as np
A = np.mat('''1 2 3;
4 5 6;
7 8 9''')
m, n = A.shape
rowranges = [
(np.arange(i, i+2) % m)[:, np.newaxis]
for i in range(m)
]
colranges = [
np.arange(j, j+2) % n
for j in range(n)
]
for rowrange in rowranges:
for colrange in colranges:
print(A[rowrange, colrange])
Output (Attempt This Online!):
[[1 2]
[4 5]]
[[2 3]
[5 6]]
[[3 1]
[6 4]]
[[4 5]
[7 8]]
[[5 6]
[8 9]]
[[6 4]
[9 7]]
[[7 8]
[1 2]]
[[8 9]
[2 3]]
[[9 7]
[3 1]]
3D case
m3d = np.array(list(range(27))).reshape((3,3,3))
m3d[np.ix_(range(-1,1), range(-1,1), range(-1,1))]
Output:
array([[[26, 24],
[20, 18]],
[[ 8, 6],
[ 2, 0]]])