Why does Python start at index -1 (as opposed to 0) when indexing a list from the end? [duplicate]
Solution 1:
To explain it in another way, because -0
is equal to 0
, if backward starts from 0
, it is ambiguous to the interpreter.
If you are confused about -
, and looking for another way to index backwards more understandably, you can try ~
, it is a mirror of forward:
arr = ["a", "b", "c", "d"]
print(arr[~0]) # d
print(arr[~1]) # c
The typical usages for ~
are like "swap mirror node" or "find median in a sort list":
"""swap mirror node"""
def reverse(arr: List[int]) -> None:
for i in range(len(arr) // 2):
arr[i], arr[~i] = arr[~i], arr[i]
"""find median in a sort list"""
def median(arr: List[float]) -> float:
mid = len(arr) // 2
return (arr[mid] + arr[~mid]) / 2
"""deal with mirror pairs"""
# verify the number is strobogrammatic, strobogrammatic number looks the same when rotated 180 degrees
def is_strobogrammatic(num: str) -> bool:
return all(num[i] + num[~i] in '696 00 11 88' for i in range(len(num) // 2 + 1))
~
actually is a math trick of inverse code and complement code, and it is more easy to understand in some situations.
Discussion about whether should use python tricks like ~
:
In my opinion, if it is a code maintained by yourself, you can use any trick to avoid potential bug or achieve goal easier, because of maybe a high readability and usability. But in team work, avoid using 'too clever' code, may bring troubles to your co-workers.
For example, here is one concise code from Stefan Pochmann to solve this problem. I learned a lot from his code. But some are just for fun, too hackish to use.
# a strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down)
# find all strobogrammatic numbers that are of length = n
def findStrobogrammatic(self, n):
nums = n % 2 * list('018') or ['']
while n > 1:
n -= 2
# n < 2 is so genius here
nums = [a + num + b for a, b in '00 11 88 69 96'.split()[n < 2:] for num in nums]
return nums
I have summarized python tricks like this, in case you are interested.
Solution 2:
list[-1]
Is short hand for:
list[len(list)-1]
The len(list)
part is implicit. That's why the -1
is the last element. That goes for any negative index - the subtraction from len(list)
is always implicit
Solution 3:
This is the mnemonic method I use. It is just an approach of what is happening, but it works.
Don't think of those as indexes. Think of them as offsets on a circular list.
Let's use the list x = [a,b,c,d,e,f,g,h] as an example. Think about x[2] and x[-2]:
You start at offset zero. If you move two steps forward, you're going from a to b (0 to 1), and them from b to c (1 to 2).
If you move two steps backward, you're going from a to h (0 to -1), and then from h to g (-1 to -2)
Solution 4:
Because -0
in Python is 0
.
With 0
you get first element of list and
with -1
you get the last element of the list
list = ["a", "b", "c", "d"]
print(list[0]) # "a"
print(list[-1]) # d
You can also think it as shorthand for
list[len(list) - x]
where x is the element position from the back. This is valid only if0 < -(-x) < len(list)
print(list[-1]) # d
print(list[len(list) - 1]) # d
print(list[-5]) # list index out of range
print(list[len(list) - 5]) # a
Solution 5:
This idiom can be justified using modular arithmetic. We can think of indices as referring to a cell in a list obtained by walking forward i
elements. -1
referring to the last element of the list is a natural generalization of this, since we arrive at the last element in the list if we walk backwards one step from the start of the list.
For any list xs
and index i
positive or negative, the expression
xs[i]
will either have the same value as the expression below or produce an IndexError
:
xs[i % len(xs)]
The index of the last element is -1 + len(xs)
which is congruent to -1
mod len(xs)
. For example, in an array of length 12, the canonical index of the last element is 11. 11 is congruent to -1 mod 12.
In Python, though, arrays are more often used as linear data structures than circular ones, so indices larger than -1 + len(xs)
or smaller than -len(xs)
are out of bounds since there's seldom a need for them and the effects would be really counterintuitive if the size of the array ever changed.