6

Let's say I have:

>>> a = [1, 2, 3, 4]

And I want to get a reversed slice. Let's say I want the 1st and 0th elements given start_idx = 1 and stop_idx = 0:

[2, 1] 

Using the slice notation:

a[x:y:z]

What values do I use for x, y, and z using start_idx and stop_idx?

I've tried:

>>> a[start_idx:stop_idx:-1]
[2]
>>> a[start_idx:stop_idx-1:-1]
[]

Differentiation:

This question is about a slice with a negative step where the both start and end indexed elements should be included (like a closed interval in math), and the slice end index is dynamically calculated.

Understanding Python's slice notation is a generic generic question on notation: what x, y, and z mean in a[x:y:z]. It doesn't mention the reversal case.

This question differs from the other flagged duplicates as it deals with the general case where the reversed slice begin and end indices are either calculated or given by variables rather than hard coded.

Tom Hale
  • 40,825
  • 36
  • 187
  • 242
  • 2
    Asked and anwsered both 54 secs ago... peculiar. – Aleksei Maide Apr 13 '18 at 07:56
  • 1
    [This answer](https://stackoverflow.com/a/509295/5741172) provides several list slicing tricks and have indeed mentioned yours! Have a look :) – Sohaib Farooqi Apr 13 '18 at 08:02
  • 1
    @AlekseiMaide It is explicitly encouraged to ask a question and then answer it yourself (even if you know the answer before you post the question). See [It’s OK to Ask and Answer Your Own Questions](https://stackoverflow.blog/2011/07/01/its-ok-to-ask-and-answer-your-own-questions/) – DavidG Apr 13 '18 at 08:08
  • Also related: [Index entire array backwards in for loop](https://stackoverflow.com/q/42674533/364696) (which covers using `None` to explicitly request the "to end of sequence" slice behavior in the forward slice case, but using negative indices for the end of slice index). – ShadowRanger Apr 14 '18 at 04:21
  • The accepted answer in @moooeeeep's first link includes a comment on how `None` can be passed explicitly to get the same effect as leaving the end index empty. – ShadowRanger Apr 14 '18 at 04:28

2 Answers2

6

You can omit the second index when slicing if you want your reversed interval to end at index 0.

a = [1, 2, 3, 4]
a[1::-1] # [2, 1]

In general whenever your final index is zero, you want to replace it by None, otherwise you want to decrement it.

Due to indexing arithmetic, we must treat those cases separately to be consistent with the usual slicing behavior. This can be done neatly with a ternary expression.

def reversed_interval(lst, i=None, j=None):
    return lst[j:i - 1 if i else None:-1]

reversed_interval([1, 2, 3, 4], 0, 1) # [2, 1]
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
2

Here are two generic solutions:

  1. Take the forward slice then reverse it:

    >>> a[stop_idx:start_idx+1][::-1]
    [2, 1]
    
  2. Based on this answer, use a negative step and stop 1 element before the first element (plus the stop offset):

    >>> a[start_idx:stop_idx-len(a)-1:-1]
    [2, 1]
    

Comparing execution times, the first version is faster:

>>> timeit.timeit('foo[stop_idx:start_idx+1][::-1]', setup='foo="012345"; stop_idx=0; start_idx=3', number=10_000_000)
1.7157553750148509
>>> timeit.timeit('foo[start_idx:stop_idx-len(foo)-1:-1]', setup='foo="012345"; stop_idx=0; start_idx=3', number=10_000_000)
1.9317215870250948
Tom Hale
  • 40,825
  • 36
  • 187
  • 242
  • "However the question rightly shows that this doesn't work when the start and end indexes are given in variables." that doesn't seem right, it works for me, both in python 2 and 3 – William Perron Apr 13 '18 at 13:44
  • I reworded this paragraph. If you get different output from me, can you please show the input and its output? – Tom Hale Apr 13 '18 at 13:56
  • given a list `my_list = [1, 2, 3, 4]`, I used `my_list[x::y]` where `x = 1` and `y = -1` which gives me the output `[2, 1]` – William Perron Apr 13 '18 at 14:35
  • Also, the `-1` in this example doesn't mean "the last element of the entire array" but rather "amounts of steps backwards" – William Perron Apr 13 '18 at 15:10
  • @WilliamPerron With your example, you're not calculating the slice end, rather hard coding it (as null). When calculating it, it can't be null, and it's ugly to set it to `None`. How do you do it with `start_idx` and `stop_idx`? (I just updated my answer, too) – Tom Hale Apr 14 '18 at 02:52
  • Note that your second solution might give weird behaviours if stop_idx is bigger than len(a) – Olivier Melançon Apr 14 '18 at 04:13
  • @OlivierMelançon both solutions give `array([], dtype=int64)` in the case you mention. – Tom Hale Apr 14 '18 at 10:14