Are the capabilities python list slicing really asymmetric? - python

Consider the following code:
>>> a = [0, 1, 2]
>>> for i in range(len(a)):
>>> print a[0:i]
[]
[0]
[0, 1]
However, when I flip the code to take slices from the other end of the list, it no longer works:
>>> for i in range(len(a)):
>>> print a[-i:]
[0, 1, 2]
[2]
[1, 2]
The only way to make the second piece of code to work seems to be to reverse the list, do it the first way, and reverse each piece before printing it. Is there a better way to do this? I use this type of loop sometimes and I would like my code to be as clean as possible.
EDIT: In both loops, I am iterating from left to right. If I flip the direction of iteration as well for the second loop, it works. If I flip the direction of iteration for the first loop, it also has the same hiccup as the second loop:
>>> for i in range(len(a)):
>>> print a[i-1::-1]
[2, 1, 0]
[0]
[1, 0]

The first iteration, you are slicing as -0, which is just the same as slicing from 0. Only the second iteration do you slice as -1, then -2.
Perhaps you could use a range starting at a negative index:
for i in range(-len(a), 0):
print a[-i:]

Reversed slicing cannot be fully symmetric in Python because of 0-based-indexing. Specifically, it isn't possible to distinguish -0 from 0. But the symmetrical access use-case in 0-based-indexed arrays would dictate that if the 0th item is first from the left, then the -0th item (that is, negative 0) is the first from the right, but since 0 and -0 are the same entity, this poses a problem.
Consider the two orientations of a list's indices:
Forward: first item is at position 0, second item at position 1, ..., last item at position n-1
Backward: first item is at position -1, second at -2, ..., last item at position -n
The negative index notation is therefore a shorthand for (length-index): collection[-i] == collection[len(collection) - i]
Or, side by side, the indexing is:
[0, 1, 2,...,n-1]
[-n,..., -3, -2, -1 ]
Where both of the above indices are in fact identical.
To perform a symmetrical operation, your slice operator needs to correctly account for this indexing scheme:
original slice: a[0:i] == i elements from left end (at 0) with step size of 1 == a[0:i:1]
reverse slice: i elements from right end (at -1) with step size of -1.
Thus, the correct slice from the other end would be a[-1:-1-i:-1]
Note that the stop value here is the negative of i, offset by -1 to account for the -1-based-reversed-indexing, which is necessary as we are generating i using a forward based list (e.g. the range function.)
a = range(3) # [0,1,2]
for i in range(len(a)):
# forward case, next to backward case
print a[:i], a[-1:-1-i:-1]
# [] []
# [0] [2]
# [0, 1] [2, 1]

Related

How to move items with even index to the end of the list

lst = ['apple', 'orange', 'kiwi', 'ananas',
'tea', 'coffee', 'milk', 'love', 'peace']
for i in range(len(lst)):
if (i + 1) % 2 == 0:
lst.append(lst[i])
lst.pop(i)
Basically here I want the items with even index to be added at the end of this list
it works for the second item but still doesn’t for the rest of them
You can use Python's wider-step ranges:
lst = lst[1::2] + lst[0::2]
The right hand side of the plus says "grab every 2nd element starting from the first" and the left hand side says "grab every 2nd element starting from the second". This basically reconstructs the list with the odd elements first and the even elements last.
It even avoids expensive pops that make your reference algorithm O(n^2)
The problem with your approach is that the elements shift after you moved the first element. So when you are at the next element with "even" index, the element that's there was originally at an odd index. Thus, after you shift the first element, you can just directly continue with the element at the next index, which previously was two indices away, then again the next one, and so on, for half the indices in the list.
Here's an example, using a list of numbers so it's easier to see what happens. If you want odd indices instead, use range(1, len(lst)//2+1).
lst = list(range(10))
for i in range(len(lst)//2):
lst.append(lst.pop(i))
# [1, 3, 5, 7, 9, 0, 2, 4, 6, 8]
However, even if this works, modifying a list while iterating it is generally a very bad idea leading to many headaches. Also, that repeated pop(i) makes the whole operation O(n²).
Instead, it would be much faster and saner to just combine two slices of the list:
lst = list(range(10))
lst = lst[1::2] + lst[0::2]
# [1, 3, 5, 7, 9, 0, 2, 4, 6, 8]
(If you need to change the list "in-place", e.g. because of other references pointing to that list, you can replace the content of the list using an assignment to a slice: lst[:] = .... This would still not be "in-place" in the sense of not using additional memory. But if the list is so big that this is a problem, then the O(n²) running time will probably be a bigger problem anyway.)
A simple way would be to build a new list by using comprehensions:
lst2 = [v for i, v in enumerate(lst) if i%2 == 0] + \
[v for i, v in enumerate(lst) if i%2 != 0]
But it is possible to change the list in place. The rule is to start from the end of the list in order not to change oddness of indices when an element is removed
last = len(lst) - 1 # when an element is popped, the list loses one element
for i in range(len(lst), 0, -1):
if (i % 2) == 0:
val = lst.pop(i - 1) # remove element with even index
lst.insert(last, val) # insert it before last inserted
last -= 1

Negative index on nested list

Let's say I've got a nested list like this:
mylist = [[],[],[]]
and I want to insert elements at the end of the second nested list:
mylist[1].insert(-1, 1)
mylist[1].insert(-1, 2)
The output i expected was:
[[], [1, 2], []]
but instead I got:
[[], [2, 1], []]
Can somebody explain this to me? I thought the index -1 always pointed to the last position of a list.
according to documentation (https://docs.python.org/3/tutorial/datastructures.html#more-on-lists), the first argument of the insert method is the index of the element before which to insert...and -1 designates the last element of a list: so by calling insert(-1,...) the element you insert will always become the next to last element of your list.
this is easy to veryfy. if you insert yet another element
mylist[1].insert(-1, 3)
you will notice the resulting list becomes
[[], [2, 3, 1], []]
so should probably use append instead. or calculate the index dynamically like
mylist[1].insert(len(mylist[1]), 3)
If a list has for example 3 elements, they are numbered from 0 to 2 (i.e. 0, 1, 2).
If you want use negative indices, they are numbered from -3 to -1 (i.e. -3, -2, -1).
Now, if you want insert a new element at position -1, it is the same, as inserting at position 2, i.e. the inserted element will become element[2].
But element[2] will be then an element of the 4-element list, so its current position in the negative notation is not -1 but -2:
element[-4], element[-3], element[-2], element[-1]
From this page,
list.insert(index, element) means you insert element at index index. .insert(-1, value) means inserting value at the last element of the list (index=len(lst)-1). So
mylist[1].insert(1, 1)
mylist[1].insert(1, 2)
should solve the problem.
Another approach is to use append as the other one said.
mylist[1].append(1)
mylist[1].append(2)

Slice a python list from an end index to some begin index [duplicate]

Consider the following simple python code
>>> L = range(3)
>>> L
[0, 1, 2]
We can take slices of this array as follows:
>>> L[1:3]
[1, 2]
Is there any way to wrap around the above array by shifting to the left
[1, 2, 0]
by simply using slice operations?
Rotate left n elements (or right for negative n):
L = L[n:] + L[:n]
Note that collections.deque has support for rotations. It might be better to use that instead of lists.
Left:
L[:1], L[1:] = L[-1:], L[:-1]
Right:
L[-1:], L[:-1] = L[:1], L[1:]
To my mind, there's no way, unless you agree to cut and concatenate lists as shown above.
To make the wrapping you describe you need to alter both starting and finishing index.
A positive starting index cuts away some of initial items.
A negative starting index gives you some of the tail items, cutting initial items again.
A positive finishing index cuts away some of the tail items.
A negative finishing index gives you some of the initial items, cutting tail items again.
No combination of these can provide the wrapping point where tail items are followed by initial items. So the entire thing can't be created.
Numerous workarounds exist. See answers above, see also itertools.islice and .chain for a no-copy sequential approach if sequential access is what you need (e.g. in a loop).
If you are not overly attached to the exact slicing syntax, you can write a function that produces the desired output including the wrapping behavior.
E.g., like this:
def wrapping_slice(lst, *args):
return [lst[i%len(lst)] for i in range(*args)]
Example output:
>>> L = range(3)
>>> wrapping_slice(L, 1, 4)
[1, 2, 0]
>>> wrapping_slice(L, -1, 4)
[2, 0, 1, 2, 0]
>>> wrapping_slice(L, -1, 4, 2)
[2, 1, 0]
Caveat: You can't use this on the left-hand side of a slice assignment.

Reversed array slice including the first element [duplicate]

This question already has answers here:
Python reverse-stride slicing
(8 answers)
Closed 4 years ago.
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.
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]
Here are two generic solutions:
Take the forward slice then reverse it:
>>> a[stop_idx:start_idx+1][::-1]
[2, 1]
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

Regarding Python's slice notation

This line of code
print [0, 1, 2, 3, 4][0:1:1]
returns [0].
However, the following line of code:
print [0, 1, 2, 3, 4][0:0:1]
returns [].
Why is this? Based on this Explain Python's slice notation, my understanding is that the format should be:
a[start:end:step] # start through not past end, by step
So shouldn't [0, 1, 2, 3, 4][0:0:1] start and end at the 0th value, thus returning [0]?
The "end" index of a slice is always excluded from the result; i.e., listy[start:end] returns all listy[i] where start <= i < end (note use of < instead of <=). As there is no number i such that 0 <= i < 0, listy[0:0:anything] will always be an empty list (or error).
The end index in Python's slice notation is exclusive. A slice of [n:m] will return every element whose index is >= n and < m.
To simplify things a bit, try it without the step (which isn't necessary when the step value is 1):
>>> a = [0, 1, 2, 3, 4]
>>> a[0:1]
[0]
>>> a[0:0]
[]
As a general rule, the number of elements in a slice is equal to the slice's start index minus the slice's end index. I.e., the slice [n:m] will return m-n elements. This agrees with the one element (1-0) returned by [0:1] and zero elements (0-0) returned by [0:0].
(Note that this is not true if either of the slice indices is outside of the size of the array.)
For a nice visualization of how slice indices work, search for "One way to remember how slices work" at http://docs.python.org/2/tutorial/introduction.html
Note that is [0:0:1] not [0:1:1]
So:
start = 0
end = 0
step = 1
The slice [start:end:step] means it will return values that are between start and end - 1 with a certain step, so for your example:
...[0:0:1]
Values between 0 and -1, so it doesn't return anything.

Categories