1

I wrote some code to reverse every other slice of rows.

import numpy as np
test_arr = np.arange(120).reshape(12,10)
test_arr = test_arr.tolist()
def rev_rows(matrix):
    for I in range(len(matrix)): #did this to get the index of each row
        if((int(I / 4) % 2) == True): #selct rows whose index divided by 4 truncate to an odd number
            print("flip")
            matrix[I].reverse() #flip said row
        print(matrix[I])

rev_rows(test_arr)

There has to by an easier and more efficient way of doing this. I was thinking another way would be to use list operators like slices, but I can't think of one which is faster than this. Is there an easier way with numpy?

Note: the length of the matrix would be divisible by 4. i.e. (4x10), (8x10), ...

EDIT:

Sorry about the ambiguous usage of slice. What I meant by a slice is a set of rows (like test_arr[3] -> test_arr[7]). So, reversing every other slice would be reversing every row between indexes 3 and 7. I was in my little blurb about the slicing operator I was talking about this operator -> [3:7]. I don't have experience with them, and I read somewhere that they are called slicing, my bad.

Asik
  • 29
  • 3

2 Answers2

6

Update

The question wasn't very clear, so my original answer didn't solve it. Here is a working example.

With a loop

The loop version's performance is more predictable, because it's not always clear when a reshape will trigger a copy.

>>> test_arr = np.arange(120).reshape(12, 10)
>>> for i in range(4, 8):
...     test_arr[i::8] = test_arr[i::8,::-1]
>>> test_arr
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,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 49,  48,  47,  46,  45,  44,  43,  42,  41,  40],
       [ 59,  58,  57,  56,  55,  54,  53,  52,  51,  50],
       [ 69,  68,  67,  66,  65,  64,  63,  62,  61,  60],
       [ 79,  78,  77,  76,  75,  74,  73,  72,  71,  70],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])
>>> 

Without a loop

A loopless version, as asked by @KellyBundy.

>>> test_arr = np.arange(120).reshape(12, 10)
>>> temp_arr = test_arr.reshape(test_arr.shape[0]//4, 4, test_arr.shape[1])
>>> temp_arr[1::2] = temp_arr[1::2,:,::-1]
>>> test_arr = temp_arr.reshape(*test_arr.shape)
>>> test_arr
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,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 49,  48,  47,  46,  45,  44,  43,  42,  41,  40],
       [ 59,  58,  57,  56,  55,  54,  53,  52,  51,  50],
       [ 69,  68,  67,  66,  65,  64,  63,  62,  61,  60],
       [ 79,  78,  77,  76,  75,  74,  73,  72,  71,  70],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])
>>>

Original answer

You can do this with slicing: test_arr[::2] = test_arr[::2,::-1] or test_arr[1::2] = test_arr[1::2,::-1].

See the examples:

>>> import numpy as np
>>> test_arr = np.arange(120).reshape(12, 10)
>>> test_arr
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,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 40,  41,  42,  43,  44,  45,  46,  47,  48,  49],
       [ 50,  51,  52,  53,  54,  55,  56,  57,  58,  59],
       [ 60,  61,  62,  63,  64,  65,  66,  67,  68,  69],
       [ 70,  71,  72,  73,  74,  75,  76,  77,  78,  79],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])
>>> test_arr[::2] = test_arr[::2,::-1]
>>> test_arr
array([[  9,   8,   7,   6,   5,   4,   3,   2,   1,   0],
       [ 10,  11,  12,  13,  14,  15,  16,  17,  18,  19],
       [ 29,  28,  27,  26,  25,  24,  23,  22,  21,  20],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 49,  48,  47,  46,  45,  44,  43,  42,  41,  40],
       [ 50,  51,  52,  53,  54,  55,  56,  57,  58,  59],
       [ 69,  68,  67,  66,  65,  64,  63,  62,  61,  60],
       [ 70,  71,  72,  73,  74,  75,  76,  77,  78,  79],
       [ 89,  88,  87,  86,  85,  84,  83,  82,  81,  80],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [109, 108, 107, 106, 105, 104, 103, 102, 101, 100],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])
>>> 

If, instead, you wanted to reverse rows with odd indices, you'd do

>>> test_arr = np.arange(120).reshape(12, 10)
>>> test_arr[1::2] = test_arr[1::2,::-1]
>>> test_arr
array([[  0,   1,   2,   3,   4,   5,   6,   7,   8,   9],
       [ 19,  18,  17,  16,  15,  14,  13,  12,  11,  10],
       [ 20,  21,  22,  23,  24,  25,  26,  27,  28,  29],
       [ 39,  38,  37,  36,  35,  34,  33,  32,  31,  30],
       [ 40,  41,  42,  43,  44,  45,  46,  47,  48,  49],
       [ 59,  58,  57,  56,  55,  54,  53,  52,  51,  50],
       [ 60,  61,  62,  63,  64,  65,  66,  67,  68,  69],
       [ 79,  78,  77,  76,  75,  74,  73,  72,  71,  70],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 99,  98,  97,  96,  95,  94,  93,  92,  91,  90],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [119, 118, 117, 116, 115, 114, 113, 112, 111, 110]])
>>> 
jthulhu
  • 7,223
  • 2
  • 16
  • 33
  • @KellyBundy I haven't run their code, but it seems to me it's the case. Could you be more specific? Besides, I tried answering the question in natural language, which is, reverse every other row (omitting the "slice" part of the question which doesn't make much sense), rather than trying to understand what their code does. – jthulhu Aug 14 '22 at 07:22
  • @KellyBundy indeed, I'm going to add an other solution to match their code. – jthulhu Aug 14 '22 at 07:31
  • @KellyBundy I made the edit. Does it seem fine to you? – jthulhu Aug 14 '22 at 07:38
  • Yes, the new one looks ok. Seems like with "slice of rows" they mean "slice of four rows". Given that their code does that, that they did say "every other slice of rows" instead of "every other row", and that they mention multiples of 4, presumably that's what they want. I'd put that as the first solution (actually I'd remove the others). Now I just wonder whether there's an even better way without *any* Python loop... – Kelly Bundy Aug 14 '22 at 07:42
  • @KellyBundy Changed the presentation, and added a loopless version, although I wouldn't use it if performance is an issue. – jthulhu Aug 14 '22 at 16:57
  • Yea the loopless version works, but doesn't seem the best as the other answer shows because np.reshape sometimes makes copies. I was wondering in the line `test_arr = temp_arr.reshape(*test_arr.shape)` what does the * mean? Is it like pointers from C? I searched it up and did not understand. – Asik Aug 16 '22 at 01:01
  • @Asik I just added a loopless version because someone asked for. The asterisk notation is for [unpacking](https://www.scaler.com/topics/python/packing-and-unpacking-in-python/), and has nothing to do with pointers (which you never see in Python). – jthulhu Aug 16 '22 at 05:09
1

Slice after reshape:

>>> test_arr = np.arange(120).reshape(12, 10)
>>> test_arr
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,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 40,  41,  42,  43,  44,  45,  46,  47,  48,  49],
       [ 50,  51,  52,  53,  54,  55,  56,  57,  58,  59],
       [ 60,  61,  62,  63,  64,  65,  66,  67,  68,  69],
       [ 70,  71,  72,  73,  74,  75,  76,  77,  78,  79],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])
>>> reshaped = test_arr.reshape(-1, 4, 10)
>>> reshaped[1::2] = reshaped[1::2, ..., ::-1]
>>> test_arr
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,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 49,  48,  47,  46,  45,  44,  43,  42,  41,  40],
       [ 59,  58,  57,  56,  55,  54,  53,  52,  51,  50],
       [ 69,  68,  67,  66,  65,  64,  63,  62,  61,  60],
       [ 79,  78,  77,  76,  75,  74,  73,  72,  71,  70],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119]])

Note: This mainly depends on the fact that ndarray.reshape generally does not copy array. However, according to this problem, we can know some details of reshape:

After reshaping the values in the data buffer need to be in a contiguous order, either 'C' or 'F'.

And condition for returning copies:

It will do a copy if the initial order is so 'messed up' that it can't return values like this.

If a copy is returned, this method will obviously fail. But here, I attach a conjecture of my own: if we simply raise the dimension of the array, rather than trying to do things like flattening a transposed array that affect data continuity, it may not cause data discontinuity, so this method may always be effective.

Mechanic Pig
  • 6,756
  • 3
  • 10
  • 31
  • The [reshape doc](https://numpy.org/doc/stable/reference/generated/numpy.reshape.html#numpy.reshape) says *"This will be a new view object if possible; otherwise, it will be a copy"*. Do you know what "if possible" means? Is it always possible here, thanks to the guarantee that the number of rows is a multiple of 4 (and assuming there are always 10 columns)? – Kelly Bundy Aug 14 '22 at 07:59
  • 1
    @KellyBundy I found [When will numpy copy the array when using reshape()](https://stackoverflow.com/questions/36995289/when-will-numpy-copy-the-array-when-using-reshape), which doesn't always seem possible. – Mechanic Pig Aug 14 '22 at 08:12
  • 1
    @KellyBundy I noticed that the key is data continuity, but if you just simply reshape such a 2d array into a 3d array, you may never encounter the problem of data discontinuity (so reshape will never return a copy). Of course, this is just a guess. – Mechanic Pig Aug 14 '22 at 08:31
  • I'm sorry, but to confirm what this code does is: reshape the array into sections of rows of 4 -> flip the desired sections -> it some how replaces itself within the original array? Is this where your note about reshape making a copy instead of editing the original comes up. If so wouldn't it be remedied by adding a line that places the rows from reshaped into test_arr? – Asik Aug 16 '22 at 00:48