Negative subscripts

dn PythonList at DancesWithMice.info
Fri Nov 26 16:24:23 EST 2021


On 26/11/2021 22.17, Frank Millman wrote:
> In my program I have a for-loop like this -
> 
>>>> for item in x[:-y]:
> ...    [do stuff]
> 
> 'y' may or may not be 0. If it is 0 I want to process the entire list
> 'x', but of course -0 equals 0, so it returns an empty list.
...

> But in my actual program, both x and y are fairly long expressions, so
> the result is pretty ugly.
> 
> Are there any other techniques anyone can suggest, or is the only
> alternative to use if...then...else to cater for y = 0?


Here's hoping that this good-looking boy has not missed the point about
"ugly".

Is this the problem, re-stated to be fully explicit, with (almost)
zero-complexity?  [Zen of Python]

>>> x = [ "a", "b", "c", "d", "e" ]
>>> for y in [ 1, 2, 3, 4, 5 ]:
...     print( y, x[ :-y ] )
...
1 ['a', 'b', 'c', 'd']
2 ['a', 'b', 'c']
3 ['a', 'b']
4 ['a']
5 []

Whilst the code 'works', it lacks the 'zero-case' of what is not to be
included - which is an overly-complicated way of saying: misses the
'all-case'.

As observed, if we add a zero to the slice/loop-control that fails
logically (and we get more religion: "the first shall be last, and the
last shall be first". [Christian Bible])

>>> for y in [ 0, 1, 2, 3, 4, 5 ]:
...     print( y, x[ :-y ] )
...
0 []
1 ['a', 'b', 'c', 'd']
2 ['a', 'b', 'c']
3 ['a', 'b']
4 ['a']
5 []

The problem, as expressed, is that whilst there is a negative of 1, ie
-1, and other positive-integers, there is no (material) negative of 0,
ie 0 == -0 == +0.


(if the following seems pedantic and 'talking down', it is because I'm
filleting a tutorial prepared for Python-Apprentices)


Remember that 'salvation' may lie in the somewhat mysterious rules of
slicing's default values. According to the Python rule-book (not exactly
a religious text, but we must tend it with some fervor... [Built-in
Types] https://docs.python.org/3/library/stdtypes.html?highlight=slice)

«
s[i] ith item of s, origin 0	(3)

s[i:j] slice of s from i to j	(3)(4)

s[i:j:k] slice of s from i to j with step k	(3)(5)

...

(3) If i or j is negative, the index is relative to the end of sequence
s: len(s) + i or len(s) + j is substituted. But note that -0 is still 0.
»

Which anticipates the problem 'here', and only rubs salt into the wound.

Pressing on...

«
(4) The slice of s from i to j is defined as the sequence of items with
index k such that i <= k < j. If i or j is greater than len(s), use
len(s). If i is omitted or None, use 0. If j is omitted or None, use
len(s). If i is greater than or equal to j, the slice is empty.


(5) The slice of s from i to j with step k is defined as the sequence of
items with index x = i + n*k such that 0 <= n < (j-i)/k. In other words,
the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j is
reached (but never including j). When k is positive, i and j are reduced
to len(s) if they are greater. When k is negative, i and j are reduced
to len(s) - 1 if they are greater. If i or j are omitted or None, they
become “end” values (which end depends on the sign of k). Note, k cannot
be zero. If k is None, it is treated like 1.
»

...and so we see the open-closed nature of Python's ranges and
range-alike objects!


The 'secret' lies in the 'sins of omission'. As above, if either the
'start' or the 'stop' attribute is omitted, then it is treated as None, ie

x[ :-y ] becomes x[ None:-y ]

Thereafter we look at how None is interpreted. In the above example, the
'start' None becomes zero, thus:

x[ :-y ] becomes x[ None:-y ] and thus x[ 0:-y ]


The pertinent topic is the other end of the range. A 'stop' None will
become len( x ), thus putting it together with the 'start' points, the
short-hand form of a copy-operation is:

x[ : ] which becomes x[ None:None ] and thus x[ 0:len( x ) ]


Let's combine that with the idea of negative indexes/indices.
Referring-back to (3) (above).

If all of x is to be included: x[ :] is the way to go - but another way
to express that is:

x[ :len( x ) ]


If the last item in x is to be excluded: x[ :-1 ]

If the last two items ... excluded: x[ :-2 ]

(etc)

and another way to write those two specific examples is

x[ :len( x ) - 1 ]  and  x[ :len( x ) - 2 ]


and while we are taking things 'away' from the end of the iterable, the
missing x[ :len( x ) ]

is the same as x[ :len( x ) - 0 ]


Thus, by now, you've leaped ahead of me, to:

>>> for y in [ 0, 1, 2, 3, 4, 5 ]:
...     print( y, x[ :len( x ) - y ] )
...
0 ['a', 'b', 'c', 'd', 'e']
1 ['a', 'b', 'c', 'd']
2 ['a', 'b', 'c']
3 ['a', 'b']
4 ['a']
5 []

and yes, if computing y is expensive/ugly, for extra-credit, calculate
the 'stop' value outside/prior-to the for-loop!
-- 
Regards,
=dn


More information about the Python-list mailing list