vdani
vdani

Reputation: 89

Reversing a list slice in python

I am trying to reverse slice of a list in python but it returns an empty list. But when I try with whole list, it works fine. Am I missing anything here?

l = [1,2,3,4,5,6,7,8]
l[::-1] = [8, 7, 6, 5, 4, 3, 2, 1]     # <<< This worked fine.

l[2:5] = [3, 4, 5]
l[2:5:-1] = []       # <<< Expecting [5,4,3] here.

Any clues?

Upvotes: 6

Views: 4532

Answers (3)

user7711283
user7711283

Reputation:

Why does L[::-1] return a reversed list, but L[2:5:-1] an empty [] one???

The core of the confusion is the (wrong) assumption that in the [start:stop:step] slice a missing start value will always be set to the beginning (the start) and the missing stop value to the end of a list.

This assumption is not true because in Python if you don't explicit specify start and stop Python will substitute the missing start and stop values differently depending on the sign of step, setting the start to the end of the list, not the beginning, in case of a negative step value.

When you don't specify the start and/or stop values in a slice L[start:stop:step] (i.e. use L[::step]) Python will behave as follows :

  • if step has a positive value than start will be set to 0 zero ( and stop to len(L) )
  • if step has a negative value than start will be set to len(L)-1. Which value will be assigned to stop if it isn't specified can't be expressed in terms of slicing and would need conversion of slice defining values to parameters of range().

The different cases of handling not explicitly specified slice values and the feature allowing usage of negative values for indices in slices is coded in Python source code with multiple if statements which finally define the slice behavior. So to fully understand Python slicing you have to know how they are coded. And because there does not exist one and the only one making sense straightforward rule for coding slice behavior, the chosen set of decisions for designing slices does not always directly meet ones intuitive expectations.


Let's provide some code to prove the above to be true along with another ways of explaining the same as already stated above (see comments in code). The code below uses Pythons assert statement to check if the given comparisons show the right sliced values and the in Python for slice objects available .indices() method giving the parameters for range() returning the required list indices representing by the slice object:

L = [1,2,3,4,5,6]
assert L[ : :  ]    == [1,2,3,4,5,6] # << OK. No AssertionError.
assert L[ : :-1]    == L[len(L)-1:-len(L)-1:-1] == [6,5,4,3,2,1]
assert L[ : :-1]    == [6,5,4,3,2,1] # << OK. No AssertionError.
assert L[2:5:  ]    == [3,4,5]
# ===
assert L[2: :-1]    == [3,2,1]
# ^-- because with -1 step and decreasing i -= 1 starting with 0  
# ^-- the condition (2+i > -1) for list index gives [3,2,1]
assert slice(2,None,-1).indices(len(L)) == (2,-1,-1)  
# ^-- and because the None becomes -1 ( for use in range() ) 
# ===
assert L[ :5:-1]    == []
# ^-- because with -1 step and decreasing i -= 1 starting with 0  
# ^-- no list item will meet the condition (2+i > 5) for its index  
assert slice(None,5,-1).indices(len(L)) == (5,5,-1)  
# ^-- and because the None becomes 5 ( for use in range() ) 
# ===
assert L[2:5:-1]    == [] 
# ^-- because with -1 step and decreasing i -= 1 starting with 0  
# ^-- no list item will meet the condition (2+i > 5) for its index  
assert slice(2,5,-1).indices(len(L)) == (2,5,-1)  
# ^-- and because range(2,5,-1) does not return any values 
# ^-- and because you are starting at 2 and stepping -1 trying to get 
#     to 5 ( you can't count backwards from 2 to 5 ) 
# ===
assert L[2:5][::-1] == [5,4,3] # it works in different square brackets 
assert L[4:1:-1]    == [5,4,3] # '-1' is not reversing, it is stepping

Upvotes: 0

TigerhawkT3
TigerhawkT3

Reputation: 49318

Slice notation is [start:stop:step]. This means "begin at start, then increase by step until you get to end." It's similar to this construct:

counter = 0
stop = 10
step = 1
while counter < stop:
    print(counter)
    counter += step

This will produce, as expected, 0 through 9.

Now imagine if we tried it with the values you're using:

counter = 2
stop = 5
step = -1
while counter < stop:
    print(counter)
    counter += step

If we actually executed this, it would print 2, then add -1 to that to get 1, then print 1 and add -1 to that to get 0, then it would be -1, -2, and so on, forever. You would have to manually halt execution to stop the infinite loop.

If you leave start or stop empty, as in [:4] or [::-1], this indicates the beginning or end of the sequence, as determined by the step. Python will go forwards with a positive step and backwards with a negative step (trying to use a step of 0 produces an error).

>>> l[2::]
[3, 4, 5, 6, 7, 8]
>>> l[2::-1]
[3, 2, 1]
>>> l[:2:]
[1, 2]
>>> l[:2:-1]
[8, 7, 6, 5, 4]

If you specify a start, end, and step that couldn't work (an empty step defaults to 1), Python will simply return an empty sequence.

Upvotes: 0

Sorin
Sorin

Reputation: 11968

The syntax is always [start:end:step] so if you go backwards your start needs to be greater than the end. Also remember that it includes start and excludes end, so you need to subtract 1 after you swap start and end.

l[5:2:-1]= [6, 5, 4]
l[4:1:-1]= [5, 4, 3]

Upvotes: 9

Related Questions