Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python double colon with -1 as third parameter [duplicate]

Take a = [1,2,3,4,5] as an example. From my instinct, I think a[::-1] is the same as a[0:len(a):-1]. But the result turns to be wrong:

>>> a = [1,2,3,4,5]
>>> print a[::-1]
[5, 4, 3, 2, 1]
>>> print a[0:len(a):-1]
[]
>>> print a[0:3:-1]
[]
>>> print a[0:2:-1]
[]
>>> print a[:2:-1]
[5, 4]
>>> print a[:0:-1]
[5, 4, 3, 2]
>>> print a[0:-1]
[1, 2, 3, 4]

I actually can't understand the last 6 attempt's output. Could anyone give me some idea? Thanks tons.

like image 914
Judking Avatar asked Mar 13 '16 20:03

Judking


3 Answers

The syntax to be used is [start:end:step] where end is not inclusive.

Python follows some rules that are based on what is probably meant (it is possible that they are not what you meant, but language designers get the final vote in that case). We will look at the exact rules further down.

a[::-1] is a shortcut for reversing the list. If we say to work backwards without any bounds, we probably mean to move from the end to the start.

a[0:len(a):-1] means to start at 0, and by -1's go to len(a). Notice that this is not possible (we will just end up with an infinite number of negative numbers), so the result is empty. a[0:3:-1] and a[0:2:-1] have the same problem.

In the last example, a[0:-1], the negative one index means the last element. You probably mean to go from the start to the end. Note that the end point is not inclusive. This gives all but the last element (which can also be specified as a[:-1]).

With a[:2:-1], you specify a movement backwards, so you probably mean to move from the last element to the 2 index backwards. This gives [5,4] in your example. The a[:0:-1] case is similar, but movement occurs to the beginning, giving [5,4,3,2].


Referring to the cpython source code, we we can see the actual rules.

  1. If the step is not given, it is equal to 1
  2. If a start value is not given, choose a default start as 0 if step is not negative, or length -1 if it is
  3. If an end value is not given, choose a default end as length if step is not negative or -1 if it is (these values allow the last element to be kept knowing that the end point is non-inclusive)

Some adjustments are made on the start and end values if they are given

  1. If the value is less than 0, add length to it
  2. If it is still less than 0, set it to 0 if the step is non-negative, otherwise set it to -1
  3. If it is greater than or equal to the length, set it to one less than the length if the step is negative, or equal to the length otherwise

Note that in most cases, the rules determine values that can be given exactly to get an equivalent form. The exception is when the default rule determines a start or end point of -1. If we give that value, the rules for modifying values will change it.

Applying these rules we get

  • a[::-1] uses start of length-1 (default), an end of -1 (default), and step of -1
  • a[0:len(a):-1], a[0:3:-1] and a[0:2:-1] use the given values
  • a[:0:-1] uses a start of length-1 (default), an end of 0, and a step of -1
  • a[:2:-1] uses a start of length-1 (default), an end of 2, and a step of -1
  • a[0:-1] uses a start of 0, an end of length-1 (modified), and a step of 1 (default)

At this point, we can determine if a selection is possible. In the case of the empty ones, as step is negative and start<end, we now see that this isn't possible.

like image 113
Matthew Avatar answered Nov 12 '22 18:11

Matthew


For how to explain the results of your various attemps, see Matthew's anser.

Between the lines of your question, though, I read that you want to replicate the behavior of a[::-1], but with explicitly passing the start and stop of the slice.

The Python tutorial's first closer look at strings mentions this useful mnemonic:

One way to remember how slices work is to think of the indices as pointing between characters, with the left edge of the first character numbered 0. Then the right edge of the last character of a string of n characters has index n, for example:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

The first row of numbers gives the position of the indices 0...6 in the string; the second row gives the corresponding negative indices. The slice from i to j consists of all characters between the edges labeled i and j, respectively.

Of course, the same slicing rules apply to other sequences, like lists and tuples. What the tutorial does not mention[1], is that for negative steps, you have to work with shifted indices-pointing-between-the-items, like so:

 ┎───┰───┰───┰───┰───┰───┒
 ┃ P ┃ y ┃ t ┃ h ┃ o ┃ n ┃ <== For backwards slicing (i.e. negative step)
 ┞───╀───╀───╀───╀───╀───┦
     0   1   2   3   4   5
-7  -6  -5  -4  -3  -2  -1

This is because indices don't actually point between sequence items. Rather, the stop parameter of slicing is an exclusive bound, i.e., the resulting sequence will end with the last item covered by step before the one with index stop.

From this shifted pseudo-index table we can see, that to have the whole sequence reversed by slicing with explicit bounds, we must start at len(a) - 1 (or above). We further see, that there is no non-negative index we can use as stop and still have the first item of the original sequence included as the last item of the resulting sequence. [len(a)-1:0:-1] would end at the second original item already. And we can't use -1 as stop, as that is the short-hand to refer to the position after the last item independent of len(a). We have to use -len(a) - 1 as stop (or below) instead:

>>> a[len(a)-1:-len(a)-1:-1]
[5, 4, 3, 2, 1]

If one is specifying the bounds explicitly, it's probably so they can be adapted when in a later version of the code only a part of the sequence shall be used. For code to be adapted, the current revision needs to be understood, so one can make an informed decision on what to change. Needless to say, a[len(a)-1:-len(a)-1:-1] is much less intelligible than list(reversed(a)), which can easily be augmented with bounds:

>>> list(reversed(a[2:4]))  # referring to indices of original sequence
[4, 3]
>>> list(reversed(a))[1:3]  # referring to indices of reversed sequence
[4, 3]

[1] The reason why the tutorial doesn't mention that, is probably that until Python 2.2, the possibility of using a step when slicing could only be used for NumPy sequences (and maybe other third-party sequences), but wasn't supported by Python's built-in sequences list, tuple and string. Python 2.3 added the implementation for this 'extended slicing' semantics for the built-in sequence types.

like image 2
das-g Avatar answered Nov 12 '22 20:11

das-g


When there are three parameters, it indicates step. I.e. -1 means step 1 going backwards. That is why when your first argument is less then the second one, you get an empty list.

In case of two parameters, it indicates the last element counting from the end.

If you leave the first parameter empty, Python again takes the second one as the end-of-range index and starts counting from the end, since the step is negative.

like image 1
vempo Avatar answered Nov 12 '22 19:11

vempo