Getting different results while executing similar "for loops" - python

Code 1
>>> L=[0,1,2,3]
for i in range(len(L)):
print(f"Counter {i}")
for j in range(len(L)):
print(j)
if len(L)==4:
L.remove(2)
L.remove(3)
else:
pass
[Output] Counter 0
0
1
2
3
Counter 1
0
1
Counter 2
0
1
Counter 3
0
1
Code 2
>>> L=[0,1,2,3]
for i in L:
print(f"Counter {i}")
for j in L:
print(j)
if len(L)==4:
L.remove(2)
L.remove(3)
else:
pass
[Output] Counter 0
0
1
Counter 1
0
1
The two codes are similar but they are giving different results.
In the first code the length of L is 4, so the variable i in first for loop will take values 0,1,2 and 3. For i=0, j again can take 4 values. But in the second loop we make the length of list to 2. So this effect will reflect, when i=1 and so on as can be seen from the output of the code.
But in the second code, after deleting two elements of the list in the second loop, its effect becomes transparent in the next iteration of the second loop.
Why this is so? I am not able to understand whether we use for i in range(len(L)) or for i in L, its effect should be same in the output of 2 codes. In the first code, after deleting two elements of list range(L) does not change immediately, while in the second code we get different outputs.
Can someone explain why this is so?

It's because for the first one, you iterate over a range, meaning you've calculated the number of iterations from the start (4). In the second one, you iterate over a list, meaning it will keep on looping until it runs out of items in the list. Because you remove two items from the list during iteration, it will run out of items two loops sooner.
In the first piece of code, the range(len(L)) is recalculated for each iteration of the outer loop, but that doesn't mean that the outer loop's range(len(L)) will be reevaluated as well.

L=[0,1,2,3]
for i in range(len(L)) -> for i in range(4) for i in L -> for i in [0,1,2,3]
point is while you are using range(len(L)) and than trying to remove from the list it will not affect for loop as it is not dependent on List elements (i.e It is not pointing to the list). only the first time while using range(len(L))->4 now if you remove from the list or even empty the list this will not affect.
but in for i in L you are directly pointing to the list if any thing changes it will affect the for loop also.
see the below image
Image 1 as you can see it is iterating independent of the list.
Image 2 .Dependent on the list

Related

Can someone explain logic behind this python code

This python code is giving certain output someone please explain logic behind it.
l = [1,2,3,4,5]
for l[-1] in l:
print(l[-1])
output for this code is
1
2
3
4
4
You iterate through the list while assigning each value to the last value of list instead of the temporary i that we always use. So you can print every value of list except the last one cause second last one overwrites it and that's why the second last is printed twice.
Now understand for-loop behaviour in python. lets consider the following for loop:
for {val} in l: #curly braces only for the post, please do not write in python
print(val)
What python does is launch an iterator over the list l, and for every value in the list, the variable in the {} is assigned the value and prints it -> 1 2 3 4 5
now what has happened in your code is the {} contain a reference, to the iterating list itself, specifically to -1 index of the list, aka, the last element
so the for-loop still does the exact same thing, goes over the list from left to right, but also assigns the value to the last position of the same list it is iteration over.
DRY RUN:
outside for-loop: l= [1,2,3,4,5]
iteration 1: l= [1,2,3,4,1]
iteration 2: l= [1,2,3,4,2]
iteration 3: l= [1,2,3,4,3]
iteration 4: l= [1,2,3,4,4]
now for last iteration, it is iterating over and assigning to the same position, so it outputs 4 again.
TLDR:
instead of using a temp variable in the for loop, we are using a postion in our list itself, and actually modifying it as a result every iteration.

Inner while loop doesn't get executed

Found that the control never reaches the inner while loop in this snippet for deleting a duplicate number
numbers=[1,6,6,7]
k=len(numbers)
i=0
j=0
while i in range(k-1):
while j in range(i+1,k):
if numbers[i] == numbers[j]:
numbers.remove(numbers[j])
k-=1
j-=1
j += 1
i += 1
print(numbers)
Your code does not make j start at i+1. Instead it starts at zero and never changes. The inner loop never runs because 0 is outside of the range you are testing.
Try this simple change:
i=0
while i < k+1:
j=i+1
while j < k:
if numbers[i] == numbers[j]:
...
The main change is moving the initialization of j inside the first while loop, so it updates each time you go through it, and never starts out less than or equal to i.
The other change I made is much less important. Rather than using i in range(...) and j in range(...) for the while loop conditions, I just did an inequality test. This is exactly the same as what the range membership test does under the covers, but avoids unnecessary testing for things that can't happen (like j being too small, now). It also makes the loop look a lot less like a for loop, which uses for i in range(...) syntax a lot (with a different meaning).
Another issue you may run into later, with some sets with multiple sets of duplicates is that your code to remove the jth element probably doesn't do what you intend. The call numbers.remove(numbers[j]) removes the first value equal to numbers[j] from the list, which is going to be the one at index i rather than the one at index j. To delete a list item by index, you want to use del numbers[j].
It doesn't reach because j and i starts at 0 value and in the inner while loop the condition is j in range(i+1, k) which means range(1, 4) and 0 in range(1, 4) would be False. Anyways, you should avoid using j and i as counters and use a for loop instead.
But the solution is easier and doesn't need to traverse the list, if you wanna remove the duplicate values, you can do as below:
numbers = [1, 6, 6, 7]
print(list(set(numbers)))
The result is: [1, 6, 7]
You could remove duplicates from a list in Python by using the dict.fromkeys().
numbers=[1,6,6,7]
final_numbers = list(dict.fromkeys(numbers))
print(final_numbers)
In this example, we use the dict.fromkeys() method to create a dictionary from numbers variable. We then use list() to convert our data from a dictionary back to a list. Then, on the final line, we print out our revised list.
Another option is to use set.
Sets are used to store collections of unique items in Python. Unlike lists, sets cannot store duplicate values.
We can convert our list to a set to remove duplicated items.
numbers=[1,6,6,7]
final_numbers = list(set(numbers))
print(final_numbers)

conversion from if statements to efficient for loop based solution for INSERTION SORT in Python

I have the following code that performs an insertion sort for 4 items in a list. It works, but it uses IF statements. I am interested in the various solutions that show a)conversion (based on my code and variables) to a simpler more efficient algorithm using loops where there is repetition and b) a clear explanation as to where/why what has been implemented.
The code for the insertion sort using IF statements:
def main():
list=[4,1,2,6]
print("original list:",list)
cv=list[1]
if cv<list[0]:
list[1]=list[0]
list[0]=cv
print("Compare element 1 with 0:",list)
cv=list[2]
if cv<list[1]:
list[2]=list[1]
list[1]=cv
print("Compare element 2 with 1 & shift 1 upto 2:",list)
if cv<list[0]:
list[1]=list[0]
list[0]=cv
print("Compare element 2 with 0 and shift 0 and 1 up:",list)
cv=list[3]
if cv<list[2]:
list[3]=list[2]
list[2]=cv
print("Compare element 3 with 2 & shift 2 upto 3:",list)
if cv<list[1]:
list[2]=list[1]
list[1]=cv
print("Compare element 3 with 1 and shift 1 and 2 up:",list)
if cv<list[0]:
list[1]=list[0]
list[0]=cv
print("Compare element 3 with 0 and shift 0 up:",list)
main()
How does this code above translate into a for loop/loop based solution + I'd also be interested in a recursive solution, but again derived directly from this one for teaching/learning purposes.
One could start, for example, by reocgnising that each iteration is done for lengthoflist-1 times.
So:
for i in range((len(list))-1):
cv=list[i+1]
etc
I get that there will be an outer loop and an inner loop. How are these best constructed, and again, a clear explanation as to how the solution is derived from this original if-based code, is what I'm after.
First of all, never name a list "list" as it overrides the built-in list() function. Instead, I have named it l:
l = [4, 1, 2, 6]
Then, we need to simply do a for-loop that loops over the indexes in l. We need to start at index 1 though (second element) as insertion works by shifting elements left (as you know) and so we should start at the second element and go to the last (so the length of l: len(l)).
Then we want to begin a while loop. This will continue whilst the current element at i (the index) in the list is smaller than the element to its left and i is greater than 0 (so we haven't gone off the end).
For example, in the first iteration of the for-loop, i will be 1 which has the element value of 1 in l. As 1 is greater than l[i-1] (this is 4) and i which is 1 is > 0 which it is, we enter the while.
In the while loop, all we do is switch the current element and the one to its left with the nice syntax stolen from here.
Finally, the last thing to do in the while is to decrease the index by 1 i.e. move the current position to the left so we can then to another switch if it is yet again smaller than the element to its left.
So after all that explanation, here is the code:
for i in range(1, len(l)):
while l[i] < l[i-1] and i > 0:
l[i-1], l[i] = l[i], l[i-1]
i -= 1
which modifies l to:
[1, 2, 4, 6]

swapping elements in a for loop returns the same index after swap

can someone explain me the loop below:
for item in aList[start:end:1]:
aList[aList.index(item)],aList[aList.index(item)+1] = aList[aList.index(item)],aList[aList.index(item)+1]
Let s say aList = [5,2,3,6,1]. the first iteration the index would 0. in the second would be again 0 and after will be increased to 2. Thus it will choose [5,2] and will continue with [2,5] [3,6]....
Why is that and what is the right way to do this?
UPDATE: The above is just an example of a specific behavior I wanted to understand. the actual code tries to implement a coctail sort algorithm.
The actual code is like that
if f == 1:
for d in area[start:end:f]:
print area,f ,d,area[area.index(d)+1],area.index(d)+1
if d > area[area.index(d)+1]:
tmp = area.index(d)
area[tmp], area[tmp+1] = area[tmp+1],area[tmp]
area=area
end -= 1
f = ~f + 1
if f == -1:
for d in area[end:start:f]:
print area,f,d,area[area.index(d)-1],area.index(d)-1
if d < area[area.index(d)-1]:
tmp = area.index(d)
area[tmp], area[tmp-1] = area[tmp-1], area[tmp]
area=area
start += 1
f = ~f + 1
As John said, it is a bad idea to mutate a list whilst looping over it. Instead create a copy of the list and change the data in the copy of the list. However it's also unclear what you are trying to achieve so if you specify that there might be a better way to do it.
Before diving into the problem, it's worth noting there are a couple issues with the code as written.
start and end aren't defined
The assignment in the for-loop doesn't actually swap the order
Those things aside, the algorithm fails, because you iterate over every item. I'll go through 2 iterations to show you why that's so.
Let's use your example:
aList = [5,2,3,6,1]
First iteration:
item = 5
aList.index(5) = 0
so, we're going to swap aList[0] and aList[1]
at the end of the iteration, aList = [2,5,3,6,1]
Second iteration:
item = 2
Wait, why 2? Because the slice operation creates a new list that we're iterating over, and that list has the original order.
aList.index(2) = 0
so, we're going to swap aList[0] and aList[1]... again.
at the end of the iteration, aList = [5,2,3,6,1]... which is the original order.
And so on
On the third iteration, we'll wind up swapping the 3rd and 4th items. And on the fourth iteration of the loop, we'll swap them again.
You can verify this is the case by adding a print statement to your for-loop: print aList.
So how do you fix it?
You can get around this behavior by iterating over every other item, instead of every item. Try changing the slice stride (step) to 2, instead of 1.

What is fastest way to determine numbers are within specific range of each other in Python?

I have list of numbers as follows -
L = [ 1430185458, 1430185456, 1430185245, 1430185246, 1430185001 ]
I am trying to determine which numbers are within range of "2" from each other. List will be in unsorted when I receive it.
If there are numbers within range of 2 from each other I have to return "1" at exact same position number was received in.
I was able to achieve desired result , however code is running very slow. My approach involves sorting list, iterating it twice taking two pointers and comparing it successively. I will have millions of records coming as seperate lists.
Just trying to see what is best possible approach to address this problem.
Edit - Apology as I was away for a while. List can have any number of elements in it ranging from 1 to n. Idea is to return either 0 or 1 in exact same position number was received. I can not post actual code I implemented but here is pseudo code.
a. create new list as list of list with second part as 0 for each element. We assume that there are no numbers within range of 2 of each other.
[[1430185458,0], [1430185456,0], [1430185245,0], [1430185246,0], [1430185001,0]]
b. sort original list
c. compare first element to second, second to third and so on until end of list is reached and whenever difference is less than or equal to 2 update corresponding second elements in step a to 1.
[[1430185458,1], [1430185456,1], [1430185245,1], [1430185246,1], [1430185001,0]]
The goal is to be fast, so that presumably means an O(N) algorithm. Building an NxN difference matrix is O(N^2), so that's not good at all. Sorting is O(N*log(N)), so that's out, too. Assuming average case O(1) behavior for dictionary insert and lookup, the following is an O(N) algorithm. It rips through a list of a million random integers in a couple of seconds.
def in_range (numbers) :
result = [0] * len(numbers)
index = {}
for idx, number in enumerate(numbers) :
for offset in range(-2,3) :
match_idx = index.get(number+offset)
if match_idx is not None :
result[match_idx] = result[idx] = 1
index[number] = idx
return result
Update
I have to return "1" at exact same position number was received in.
The update to the question asks for a list of the form [[1,1],[2,1],[5,0]] given an input of [1,2,5]. I didn't do that. Instead, my code returns [1,1,0] given [1,2,5]. It's about 15% faster to produce that simple 0/1 list compared to the [[value,in_range],...] list. The desired list can easily be created using zip:
zip(numbers,in_range(numbers)) # Generator
list(zip(numbers,in_range(numbers))) # List of (value,in_range) tuples
I think this does what you need (process() modifies the list L). Very likely it's still optimizable, though:
def process(L):
s = [(v,k) for k,v in enumerate(L)]
s.sort()
j = 0
for i,v_k in enumerate(s):
v = v_k[0]
while j < i and v-s[j][0]>2:
j += 1
while j < i:
L[s[j][1]] = 1
L[s[i][1]] = 1
j += 1

Categories