Using index reference in a for loop iterations [duplicate] - list

Here is a snippet of code which gives the output: 0 1 2 2. I had expected the output 3 3 3 3 since a[-1] accesses the number 3 in the list. The explanation given online says "The value of a[-1] changes in each iteration" but I don't quite understand how or why. Any explanations would be great!
a = [0, 1, 2, 3]
for a[-1] in a:
print(a[-1])

While doing for a[-1] in a, you actually iterate through the list and temporary store the value of the current element into a[-1].
You can see the loop like these instructions:
a[-1] = a[0] # a = [0, 1, 2, 0]
print(a[-1]) # 0
a[-1] = a[1] # a = [0, 1, 2, 1]
print(a[-1]) # 1
a[-1] = a[2] # a = [0, 1, 2, 2]
print(a[-1]) # 2
a[-1] = a[3] # a = [0, 1, 2, 2]
print(a[-1]) # 2
So, when you are on the third element, then 2 is stored to a[-1] (which value is 1, but was 0 before and 3 on start).
Finally, when it comes to the last element (and the end of the iteration), the last value stored into a[-1] is 2 which explains why it is printed twice.

What's happening here is a list is mutated during looping.
Let's consider following code snippet:
a = [0, 1, 2, 3]
for a[-1] in a:
print a
Output is:
[0, 1, 2, 0]
[0, 1, 2, 1]
[0, 1, 2, 2]
[0, 1, 2, 2]
Each iteration:
reads value from position currently pointed by internal pointer
immediately assigns it to last element in list
after that last element is printed on standard output
So it goes like:
internal pointer points to first element, it's 0, and last element is overwritten with that value; list is [0, 1, 2, 0]; printed value is 0
internal pointer points to second element, it's 1, and last element is overwritten with that value; list is [0, 1, 2, 1]; printed value is 1
(...)
at last step, internal pointer points to last element; last element is overwritten by itself - list does not change on last iteration; printed element also does not change.

This code
a = [0, 1, 2, 3]
for a[-1] in a:
print(a[-1])
is equivalent to
a = [0, 1, 2, 3]
for i in range( len(a) ):
a[-1] = a[i] # update last element of list(updated) with element at i'th position
print(a[-1]) # print last element of list
the output will be 0 1 2 2
Explanation:
len(a) = 4, range( len(a) ) = [0, 1, 2, 3]
1st loop, current list = [0, 1, 2, 3], i = 0 => a[i] = a[0] = 0, updated list = [0, 1, 2, 0], last element a[-1] = 0
2nd loop, current list = [0, 1, 2, 0], i = 1 => a[i] = a[1] = 1, updated list = [0, 1, 2, 1], last element a[-1] = 1
3rd loop, current list = [0, 1, 2, 1], i = 2 => a[i] = a[2] = 2, updated list = [0, 1, 2, 2], last element a[-1] = 2
4th loop, current list = [0, 1, 2, 2], i = 3 => a[i] = a[3] = 2, updated list = [0, 1, 2, 2], last element a[-1] = 2

Related

Can someone help me understand how this piece of code to sort elements of a list in descending order works?

I just would like to understand the passages of how this piece of code operates:
llist = [5, 2, 1, 4, 3]
for i in range(len(llist)):
for j in range(i+1):
if llist[j] < llist[i]:
temp = llist[i]
llist[i] = llist[j]
llist[j] = temp
print(llist)
I tried to meticulously go through every passage of the nested loop, but I can't understand how at the end of all that new-variable-assigning the elements of the list get sorted from highest to lowest.
Thank you very much to anyone who can help
With a bit of printing:
llist = [5, 2, 1, 4, 3]
for i in range(len(llist)):
print()
print(f'current pivot is {llist[i]} on position {i}')
for j in range(i+1):
if llist[j] < llist[i]:
print('-----------------------------------------------------------------')
print((f'\t {llist[j]} in position {j} and {llist[i]} on position {i} are in'
' the wrong order'))
print(f'\t before swapping our list is : {llist}')
temp = llist[i]
llist[i] = llist[j]
llist[j] = temp
print(f'\t after swapping we have {llist}')
print('-----------------------------------------------------------------')
print(f'list at the end of iteration: {llist}')
You get:
current pivot is 5 on position 0
list at the end of iteration: [5, 2, 1, 4, 3]
current pivot is 2 on position 1
list at the end of iteration: [5, 2, 1, 4, 3]
current pivot is 1 on position 2
list at the end of iteration: [5, 2, 1, 4, 3]
current pivot is 4 on position 3
-----------------------------------------------------------------
2 in position 1 and 4 on position 3 are in the wrong order
before swapping our list is : [5, 2, 1, 4, 3]
after swapping we have [5, 4, 1, 2, 3]
-----------------------------------------------------------------
-----------------------------------------------------------------
1 in position 2 and 2 on position 3 are in the wrong order
before swapping our list is : [5, 4, 1, 2, 3]
after swapping we have [5, 4, 2, 1, 3]
-----------------------------------------------------------------
list at the end of iteration: [5, 4, 2, 1, 3]
current pivot is 3 on position 4
-----------------------------------------------------------------
2 in position 2 and 3 on position 4 are in the wrong order
before swapping our list is : [5, 4, 2, 1, 3]
after swapping we have [5, 4, 3, 1, 2]
-----------------------------------------------------------------
-----------------------------------------------------------------
1 in position 3 and 2 on position 4 are in the wrong order
before swapping our list is : [5, 4, 3, 1, 2]
after swapping we have [5, 4, 3, 2, 1]
-----------------------------------------------------------------
list at the end of iteration: [5, 4, 3, 2, 1]
So we you can see, every time it finds that a number on the list has another number to its left that is smaller than the number at hand, then it swaps them.
For 5, it does nothing as there are no numbers to its left.
For 2, it does nothing as 5 is the only number to the left and it's > 2.
For 1, it does nothing as 5 and 2 are the only numbers to the left and they are both > 1.
For 4, it finds 2 as a number to its left, so we swap them and have:
[5,2,1,4,3] -> [5,4,1,2,3]
So now we continue and find 1 to the left of 2 (since now 2 is in the 3rd position), so we swap again
[5,4,1,2,3] -> [5,4,2,1,3]
Does that help?

List negative indexing of 0; x[-0]

Can anyone explain how works Negative index of 0?
x = [2, 3, 5, 6]
print(x[-0])
output: 2
What you enter as the index gets evaluated. For example :
x = [2, 3, 5, 6]
print(x[2 - 1])
It prints 3 because it is evaluated as print(x[1]). Here you used -0 so it is the same as 0.

check if the rows from a set of indices are duplicates or not and reconstruction

I have a 2D binary array where the value can take 0 and 1 only.
I have a set of indices to check whether the entries of the binary matrix for those indices are duplicate or not. I want to get the matrix with duplicate rows removed and the set of duplicate indices.
For example,
>>>> a
array([[1, 0, 1, 0],
[0, 0, 1, 1],
[1, 0, 1, 0],
[0, 0, 1, 1],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
])
I am given set of indices (0,2,3,4,6,7). From the set, the rows corresponding to (0,2) and (4,6,7) are duplicates. I want the resulting matrix with the duplicates removed (as shown below)
>>>> b
array([[1, 0, 1, 0],
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 0],
[1, 1, 1, 0],
])
and a method for reconstruction of the matrix 'a' from 'b'
If the order in the output array is not relevant, then you can probably just use Eelco Hoogendoorn's answer. However, if you want to keep the same relative order as in the original array, here is another possible approach.
import numpy as np
a = np.array([
[1, 0, 1, 0],
[0, 0, 1, 1],
[1, 0, 1, 0],
[0, 0, 1, 1],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
[1, 1, 1, 0],
])
idx = np.array([0, 2, 3, 4, 6, 7])
# Make an array of row numbers
r = np.arange(len(a))
# Replace row numbers in idx with -1
# (use assume_unique only if indices in idx are unique)
r[np.isin(r, idx, assume_unique=True)] = -1
# Add the column to the array
a2 = np.concatenate([a, r[:, np.newaxis]], axis=-1)
# Find unique indices and inverse indices
_, uniq_idx, inv_idx = np.unique(a2, return_index=True, return_inverse=True, axis=0)
# Sort indices to make output array and inverse indices
s = np.argsort(uniq_idx)
a_uniq = a[uniq_idx[s]]
inv_idx = s[inv_idx]
print(a_uniq)
# [[1 0 1 0]
# [0 0 1 1]
# [0 0 1 1]
# [1 1 1 0]
# [1 1 1 0]]
print(np.all(a_uniq[inv_idx] == a))
# True
EDIT: Some further explanation.
The idea in the solution above is to apply np.unique, but in a way that the rows that are not included in idx are not affected by it. In order to do that, you can just add a new number to each row. For the rows included in idx, this number will always be -1, and for the rest of rows it will be a different number for each. That way, it is impossible that rows that are not in idx get removed by np.unique. In order to do that, I build r, first with np.arange(len(a)), which gives you a number per row:
[0 1 2 3 4 5 6 7]
Then I check which of those are in idx with np.isin(r, idx, assume_unique=True) (assume_unique can only be used if elements in idx are guaranteed to be unique), so r[np.isin(r, idx, assume_unique=True)] = -1 will turn all indices idx into -1:
[-1 1 -1 -1 -1 5 -1 -1]
That is added as new column to a into a2:
[[ 1 0 1 0 -1]
[ 0 0 1 1 1]
[ 1 0 1 0 -1]
[ 0 0 1 1 -1]
[ 1 1 1 0 -1]
[ 1 1 1 0 5]
[ 1 1 1 0 -1]
[ 1 1 1 0 -1]]
Now it's just a matter of applying np.unique to a2. As expected, only rows in idx may be eliminated. However, since we want to keep the original relative order, we cannot use the output of np.unique, because it is sorted. We use return_index and return_inverse to get the indices that make the array of unique rows and the indices that get you back to the original array, and actually discard the new array.
To form the final array, you need to sort uniq_idx to keep the relative order, and then inv_idx accordingly. np.argsort gives you the indices that sort uniq_idx into s. uniq_idx[s] is just the array of unique row indices sorted, and s[inv_idx] will map every inverse index in inv_idx to the corresponding one in the resorted array. So, finally, a[uniq_idx[s]] gives you the output array, and the new inv_idx takes you back to the original one.
It feels like you could phrase your question at a higher level to get a more elegant solution; but this seems to solve the literal problem as stated.
idx = [0,2,3,4,6,7]
b = np.concatenate([np.unique(a[idx], axis=0), np.delete(a, idx, axis=0)], axis=0)

How to switch values in a list to print an alternated version of the list?

I'm having some trouble figuring out how to switch numbers in a long list.
For example if were to have a list:
numbers = [1,2,3,4,5,6,7,8]
and wanted to instead print it in the form of:
numbers_2 = [2,1,4,3,6,5,8,7]
such that each pair would be switched, using a for-loop. I thought about doing something like:
for i in range(0, len(numbers), 2):
But wasn't really able to get much further.
Loop every second index and swap two adjacent items:
numbers = [1,2,3,4,5,6,7,8]
for i in range(1, len(numbers), 2):
numbers[i-1], numbers[i] = numbers[i], numbers[i-1]
Not sure about the other answers, but this one will also work with a list of an uneven length, and leave the last item untouched.
Take 2 halves, and rearrange them:
numbers = [1,2,3,4,5,6,7,8]
first = numbers[::2]
second = numbers[1::2]
numbers_2 = sum(map(list, zip(second, first)), [])
Try this:
def swap_array_elements (a):
for i in range (0, len(a) - 1, 2):
a[i], a[i +1] = a[i + 1], a[i]
return a
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print (swap_array_elements (a))
# prints: [1, 0, 3, 2, 5, 4, 7, 6, 9, 8]

Remove adjacent element in a list in python

I am trying to do a simple python program that removes all the adjacent elements in a list
def main():
a = [1, 5, 2, 3, 3, 1, 2, 3, 5, 6]
c = len(a)
for i in range (0, c-2):
if a[i] == a[i+1]:
del a[i]
c = len(a)
print a
if __name__ == '__main__':
main()
and the output is
[1, 5, 2, 3, 3, 2, 3, 5, 6] which is all fine!
If change the a list to a = [1, 5, 2, 3, 3, 1, 2, 2, 5, 6]
then it gives an error
index list out of range
**if a[i] == a[i+1]**
It shouldn't be complaining about the index out of range as I am calculating the len(a) every time it deletes an element in the list. What am I missing here?
for i in range (0, c-2):
This is not like a for loop in some other languages; it’s iterating over a list returned (once) by range. When you change c later, it does not affect this loop.
You can use while instead:
c = len(a)
while i < c - 2:
if a[i] == a[i + 1]:
del a[i]
c = len(a)
else:
i += 1
There’s also itertools.groupby:
import itertools
def remove_consecutive(l):
return (k for k, v in itertools.groupby(l))
Here's a slightly different approach:
origlist=[1, 5, 2, 3, 3, 1, 2, 3, 5, 6]
newlist=[origlist[0]]
for elem in origlist[1:]:
if (elem != newlist[-1]):
newlist.append(elem)
The itertools answer above may be preferred, though, for brevity and clarity...