How to update loop variable with range function? [duplicate] - python

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 2 years ago.
I want to remove duplicates from a list. I know it can be done easily with list(dict.fromkeys(my list)), but I want to do it by iterating through the list. When I am removing a duplicate number from the list, the loop variable j causes an Index error : list index out of range later in subsequent iterations, even though I am changing the range input. I am wondering how to change loop variable inside loop ?
def rem_duplicate(seq):
for i in range(len(seq)):
num = seq[i]
count = 0
length = len(seq)
for j in range(length):
if seq[j] == num:
count += 1
if count > 1:
del seq[j]
length=length-1
return seq
my_list = [2, 3, 4, 5, 6, 7, 8, 3, 4, 1, 5, 2, 5, 7, 9]
print(rem_duplicate(my_list))

When you enter the inner loop, the range() function is called with current value of length, say, 3. So, your loop would look like for j in [1, 2, 3] and changing length won't affect changing the mentioned list.
To achieve what you want, and also when mutating the size of the list you're iterating on, it's always better to use a while loop:
j = 0
while j < length:
if seq[j] == num:
count += 1
if count > 1:
del seq[j]
length=length-1
j += 1

range(len(seq))
Will return an inmutable sequence, that will not be affected by add / del operations over the original list.
Take a look to the previous answers to the same question (using a while): Python: remove duplicate items from a list while iterating

Related

Is there a way to find the position of my for loop variable in a integer list [duplicate]

This question already has answers here:
Accessing the index in 'for' loops
(26 answers)
Closed 3 months ago.
For example, num = [4, 6, 2, 5, 7]
for i in num:
for j in num:
j = num[i+1]
Is there a way to find if i is in the 0 position, 1 position, 2 position, ...
so that I can make it were j = what position i is in +1
I also want to make it were if, lets say i was in position 1;
if i == i+1:
num.remove(i)
I already tried i+1 just doing i plus one and i could do a bunch of if statements and just make it do 1 over but i have like 4 variable for the for loop all in different position of i and I'm worried it will say list(x) out of index. I also tried .find put it didn't work for a list.
Also, I need to make it as too were it doesn’t change the original value of the integer so that I can add them up, would I have to make too lists.
Yes, what you're looking for is the enumerate function, which takes in a list and gives you both the index and the value of each element in the list:
nums = [4, 6, 2, 5, 7]
for index, value in enumerate(nums):
print(index, value)
# will print
# (0, 4)
# (1, 6)
# (2, 2)
# (3, 5)
# (4, 7)
Some extra info from RealPython: https://realpython.com/python-enumerate/

What is the difference between len(x) and len(x)-1? [duplicate]

This question already has answers here:
Why does range(start, end) not include end? [duplicate]
(11 answers)
Closed 2 years ago.
I'm sorry for the very basic question, this is a concept I can't quite grasp.
It is my understanding that the first element in a Python list has index [0]. This means that len(x)-1 should be the last element in my list x.
So, if I write a loop with:
for i in range(0, len(x)-1):
#do stuff here
This should mean that I want I to go from the first element of my list to the last one.
However, I always find written:
for i in range(0, len(x)):
#do stuff here
Why is that?
Which one is correct?
range(0,x) will loop up to the value of x, so < x as opposed to <= x. For example, range(0, 3) will loop over the indices 0, 1, 2.
Which means the second one is correct, as if x = [4, 5, 6, 7] (len 4) - the maximum index is 3 so you'd want to loop over the indices 0, 1, 2, 3.
range(x, y) means to start with x and iterate till y-1 so when you use for i in range(0, len(x)-1):, it will iterate from 0 to len(x) - 2.
For example if, len(x) = 3, the statement for i in range(0, len(x)-1): will iterate from 0 to 1 so that's why we use for i in range(0, len(x)): to iterate over all the elements in the list.

Why I get the list unchanged when using this code?

I want to delate the duplicated elements in a list using this structure but the list remains unchanged when I use this code. Could anyone help me please?
e.g.,item=[1,2,3,4,5,6,7,8,9,1,2,6,7]
def duplicated(item):
i=0
j=0
while i<len(item):
while j<len(item):
if item[j]==item[i] and i!=j:
del item[j]
j+=1
i+=1
return item
To address why your code isn't working, it is due to the fact that you initialise j at the start of the function, so after the first iteration of the i loop, it is len(item)-1 and then never get's reset for future loops.
This means that you miss many duplicates.
So since we need to initialise it each loop, we still need to know what to. If we initialise it as 0, then we are checking for duplicates behind the current j value in the list so this is a waste of time. To improve efficiency, we should initialise it as i+1 so that we check numbers after i for duplicates as we already know that there are no duplicates of the value at i before the index i in the list. This also simplifies the if as we no longer need to check i != j.
And one final thing is that when you delete j with del, all the indexes after are now shifted down by one. So we also need to subtract 1 from j so that we now check the element straight after the one we just deleted which is now at the same index of the one we just deleted (since they are shifted down).
So, in code:
def duplicated(item):
i = 0
while i < len(item):
j = i + 1
while j < len(item):
if item[j] == item[i]:
del item[j]
j -= 1
j += 1
i += 1
return item
and it works with the examples people have given:
>>> duplicated([1,2,3,4,5,6,7,8,9,1,2,6,7])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> duplicated([1,2,3,4,5,6,7,8,9,1,1,2,6,7])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However, this solution is current of complexity O(n^2) since we are using nested loops so the time taken is proportional to the size of the input list squared.
But if we were able to modify the algorithm to use only one loop, we would reduce the complexity to O(n). This can be done by using a set for lookup. Adding and checking if an element is in the set is O(1) (average case) so we can use them to speed things up.
So if we have a set which contains the elements we have already seen, then for each future element, if it is already in the seen set, we delete it, otherwise we add it to this set.
So, in code:
def duplicated(item):
seen = set()
i = 0
while i < len(item):
if item[i] in seen:
del item[i]
else:
seen.add(item[i])
i += 1
return item
And I can confirm that this passes the test cases above as well.
One last thing I should point out is that when deleting the element here, I did not subtract from the pointer, this is because before we subtracted as we knew it would later be incremented and we wanted it to be the same, however here, it is only incremented in the else block, so if we don't do anything, it will stay the same.
Actually variable j should start with next item of what i picks.
def duplicated(item):
i=0
while i<len(item):
j = i+1
while j<len(item):
if item[j]==item[i] and i!=j:
del item[j]
j -= 1
j+=1
i+=1
return item
You should reinitialize your j var at each turn inside of the i loop, otherwise, j is always going to be equal to len(item) after the first iteration.
def duplicated(item):
i=0
while i<len(item):
j=0
while j<len(item):
if item[j]==item[i] and i!=j:
print(i,j )
del item[j]
j+=1
i+=1
return item
However, The best way, if you don't care about your list order, to do what you want will probably be to convert your list to a set and then back to a list, has a set can only have distinct elements.
def duplicated(item):
return list(set(item))
You need to reinitilaze j at start of nested while loop each time:
def duplicated(item):
i=0
j=0
while i<len(item)-1:
j=i+1
while j<len(item):
if item[j]==item[i]:
del item[j]
j -= 1
j+=1
i+=1
return item
OUT
[1, 2, 3, 4, 5, 6, 7, 8, 9]
However you can try below simpler code it will remain the insertion order of list
def duplicated(item):
unique_list=[]
for i in item:
if i not in unique_list:
unique_list.append(i)
return unique_list

Add a 0 after each even number in list [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I am trying to add a 0 after each even number, every time i run the code it infinitely prints the value of i, which is 0 every time.
I've tried this so far:
def linear_list (n):
x = []
for y in range (1, n+1):
x.append (y)
return x
n = int (input ("Enter the lenght of the list: "))
alist = linear_list (n)
for i in alist:
print (i)
if i % 2 == 0:
alist.append(0)
print (alist)
But for i >= 2my code is printing infinite zeroes:
Enter the lenght of the list: 5
0
0
0
0
...
Expected output:
[1, 2, 0, 3, 4, 0, 5, 6, 0, 7, 8, 0, 9, 10, 0]
How can achieve the correct list?
Make it work, make it right, make it fast
Here the make it work part.
You are modifying the list and iterating it at the same time.
Try:
otherlist = []
for i in alist:
print (i)
otherlist.append(i)
if i % 2 == 0:
otherlist.append(0)
print (otherlist)
You're increasing the alist length every time you append to it, and since you're looping through it you will never exit the loop - the list never ends. You don't want to change the list, but the value within it, so enumerate it:
for i, v in enumerate(alist):
print (v)
if v % 2 == 0:
alist[i] = v * 10 # assuming it's a number, use v + "0" if its a string
The iterator create by the for loop is a separate object from the list you iterate over. Your for loop is similar to a while loop like
itr = iter(a)
while True:
try:
i = next(itr)
except StopIteration:
break
if i % 2 == 0:
a.append(0)
a.append always adds a 0 to the end of the list, without affecting the current position of the iterator. Since 0 is even, once the iterator reaches the what was the end of the list when you started the loop, it sees a 0, and so another 0 gets added to the end of the list. It continues reading the next 0 and adding another 0, on and on, forever.
Technically, you can do what you want, but it's rather tricky and very easy to get wrong. (It took me about 8 tries to get this example right.) The trick is to create a separate iterator explicitly, one that you can access in the body of the loop, rather than letting the for loop generate the iterator for you. This allows you to insert a 0 after the current value in the list, then skip over the new 0 so that you won't see it on the next iteration of the loop.
Don't actually do this if you can avoid it: it is far simpler to create a new list that replaces the old list after the loop.
That said, on with the show:
a = range(10)
itr = enumerate(a) # A list of index, value pairs
for index, value in itr:
if value % 2 == 0:
i = index + 1
a[i:i] = [0] # Put a 0 after the current position
next(itr) # Skip over the 0 you just added
assert a == [0, 0, 1, 2, 0, 3, 4, 0, 5, 6, 0, 7, 8, 0, 9]
You can shorten this a bit by starting the index at 1 instead of 0, effectively pre-adding 1 to each index before you need it.
a = range(10)
itr = enumerate(a, start=1)
for i, value in itr:
if value % 2 == 0:
a[i:i] = [0]
next(itr)

Traversing a Python list and making in-place changes

My task is to remove all instances of one particular element ('6' in this example) and move those to the end of the list. The requirement is to traverse a list making in-line changes (creating no supplemental lists).
Input example: [6,4,6,2,3,6,9,6,1,6,5]
Output example: [4,2,3,9,1,5,6,6,6,6,6]
So far, I have been able to do this only by making supplemental lists (breaking the task's requirements), so this working code is not allowed:
def shift_sixes(nums):
b = []
c = 0
d = []
for i in nums:
if i == 6:
b.insert(len(nums),i)
elif i != 6:
c = c +1
d.insert(c,i)
ans = d + b
return ans
I've also tried list.remove() and list.insert() but have gotten into trouble with the indexing (which moves when I insert() then move the element to the end): For example -
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in nums:
if i == 6:
nums.remove(i)
nums.insert(nums[len(nums)-1], 0)
elif i != 0:
i
shift_sixes(a)
Additionally, I have tried to use the enumerate() function as follows, but run into problems on the right hand side of the b[idx] assigment line:
for idx, b in enumerate(a):
a[idx] = ???
Have read other stackoverflow entries here, here and here, but they do not tackle the movment of the element to one end.
Would appreciate any help on this list traversal / inplace switching issue. Many thanks.
EDIT
#eph - thank you. this is indeed an elegant response. I am sure it will pass my 'no new list' requirement? I surely intend to learn more about lambda and its uses
#falsetru - thank you for the reminder of the append/pop combination (which I tried to do in my original query via list.remove() and list.insert()
#tdelaney - thank you as well. somehow your response is closest to what I was attempting, but it seems not to pass the test for [0, 0, 5].
It is a bad idea to modify list while traverse. You can either make a copy to traverse, or generate a new list during traverse.
In fact, the question can be done in many ways, such as:
>>> a.sort(key = lambda i: i == 6)
>>> a
[4, 2, 3, 9, 1, 5, 6, 6, 6, 6, 6]
Iterating the list reverse way, pop the element if it's 6, then append it.
xs = [6,4,6,2,3,6,9,6,1,6,5]
for i in range(len(xs)-1, -1, -1): # 10 to 0
if xs[i] == 6:
xs.append(xs.pop(i))
Why not try something like this?
Basically, the approach is to first count the number of values.
If 0, then returns (since Python produces a ValueError if the list.index method is called for an element not in the list).
We can then set the first acceptable index for the value to be the length of the list minus the number of occurrences it exists in the list.
We can then combine list.pop/list.append to then traverse the list until all the values desired occur at the end of the list.
def shift_value(lst, value):
counts = lst.count(value) # 5
if not counts:
return lst
value_index = len(lst) - counts
index = lst.index(value)
while index != value_index:
lst.append(lst.pop(index))
index = lst.index(value)
return lst
lst = [6,4,6,2,3,6,9,6,1,6,5]
print(shift_value(lst, 6))
EDIT: This is horribly inefficient, better answer suggested above.
This requires O(n^2) time, rather than O(n) time.
The key term here is "In Line". The way you do that is move num[i] = num[i+1] for each i to the end of the list.
def shift_sixes(num):
for i, val in enumerate(num):
if val == 6:
# shift remaining items down
for j in range(i,len(num)-1):
num[j] = num[j+1]
# add 6 at the end
num[-1] = 6
return num
print(shift_sixes([1,9,4,6,2,7,8,6,2,2,6]))
print(shift_sixes([1,2,3]))
print(shift_sixes([6]))
print(shift_sixes([3]))
Use two runners. First from front to end checking for 6s, second from end to front pointing to last item that's not a 6. Keep swapping (a[i+1], a[i] = a[i], a[i+1]) until they meet.
Catch: this is not stable like in a stable sort. But I don't see that as a requirement.
Will try to write working code when in front of a python interpreter with a keyboard.
In case you need a stable sort (i.e. order of elements that are not 6 should remain the same), then the solution is:
def move_to_end(data, value):
current = 0 # Instead of iterating with for, we iterate with index
processed = 0 # How many elements we already found and moved to end of list
length = len(data) # How many elements we must process
while current + processed < length: # While there's still data to process
if data[current] == value: # If current element matches condition
data.append(data.pop(current)) # We remove it from list and append to end
processed += 1 # Our index remains the same since list shifted, but we increase number of processed elements
else: # If current element is not a match
current += 1 # We increase our index and proceed to next element
if __name__ == '__main__':
print
print 'Some testing:'
print
for test_case in (
[1, 9, 4, 6, 2, 7, 8, 6, 2, 2, 6], # Generic case
[6, 6, 6, 6], # All items are 6
[1, 7], # No items are 6
[], # No items at all
):
print 'Raw:', test_case
move_to_end(test_case, 6)
print 'Becomes:', test_case
print
Note that this solution retains the order of not only non-matching elements, but of matching elements as well. So for example, if you change the check condition from "equal to 6" to "is an even number", all elements matching the condition will be moved to the end of list while retaining their order among themselves.
Why not keep it simple?
a = [6,4,6,2,3,6,9,6,1,6,5]
def shift_sixes(nums):
for i in range(0,len(nums)):
if nums[i] == 6:
nums.append(nums.pop(i))
>>> shift_sixes(a)
>>> a
[3, 9, 1, 5, 2, 4, 6, 6, 6, 6]

Categories

Resources