Loop while list not empty [duplicate] - python

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 5 months ago.
I need to loop over a list until list is not empty according to a condition, also
This is the code I wrote but is returning a 'list index out of range' error.
What is the problem? Any suggestions on how to eventually improve this?
l = [0, 1, 0, 0, 1, 1]
removed= []
while l:
for i in range(len(l)):
if l[i]>0:
l.remove(l[i])
# store the index processing order
removed.append(i)
else:
continue
l = [x+1 for x in l]

The problem is the list l gets smaller after calling l.remove(value), but subscript 'i' still try to index the original l.
Based on the above analysis, one solution is to keep l unchanged in the inner loop, the other is to keep the unseen i reduced along with l.
# Create new lists to keep `l` unchanged in the inner loop
def method1():
l = [0, 1, 0, 0, 1, 1]
removed= []
while l:
next_l = []
[next_l.append(v) if v <= 0 else removed.append(i) for i, v in enumerate(l)]
l = [x+1 for x in next_l]
return removed
def method2():
l = [0, 1, 0, 0, 1, 1]
removed= []
while l:
num_del = 0 # record number of deletions in the inner loop
for i in range(len(l)):
if l[i-num_del]>0:
l.remove(l[i-num_del])
num_del += 1
# store the index processing order
removed.append(i)
else:
continue
l = [x+1 for x in l]
return removed
assert method1() == method2()
# output [1, 4, 5, 0, 1, 2]
But I guess you expect the result [1, 4, 5, 0, 2, 3], i.e., record the processing order with subscript in the original list. If so, try this:
l = [0, 1, 0, 0, 1, 1]
el = list(enumerate(l))
removed = []
bound = 0
while len(removed) != len(l):
removed.extend(list(filter(lambda iv: iv[1] > bound, el)))
el = list(filter(lambda iv: iv[1] <= bound, el))
bound -= 1
removed, _ = zip(*removed)

IIUC - it looks like you just want the index of the removed values and keep the values in the original list if they are less than or equal to and then +1 to the value
l = [0, 1, 0, 0, 1, 1]. # your list
keep_idx, lst = zip(*[(idx, i+1) for idx, i in enumerate(l) if i<=0])
print(list(keep_idx)) # -> [0, 2, 3]
print(list(lst)). # -> [1, 1, 1]

Since you're removing items from list while trying to loop through it, your list length keeps going down but the range doesn't account for it. What you could do is looping through the list backwards.
One solution is using
range(len(list_name)-1, -1, -1)
This is to loop through the array backwards and stop the out of range error

EDIT: I was wrong here, but I will keep the answer up so people can see where I went wrong. As Ignatius pointed out, range() is exclusive of the final item.
Original answer:
Lists use Zero-based indexing, so the first item is item 0.
An example list:
l = [1, 2, 3, 4, 5]
l contains 5 items, but indexes start at 0, so '1' is l[0] and '5' is l[4].
This is why you're getting an out of range exception. In your code you are looping up to len(l) which is 6, but l[6] is out of bounds. l[5] is the final item in your list.
Solution:
for i in range(len(l)-1)):

Related

Why do I get an IndexError: list index out of range

In my code I am getting an index error - IndexError: list index out of range. Could you please 1) explain why is this and then 2) make some corrections to my code? Thank you for your answer in advance
x = [1, 2, 3, 4, 5]
for i in range(len(x)):
if x[i] % 2 == 0:
del x[i]
When you use del you reduce the size of your array but the initial loop goes through the initial size of the array, hence the IndexError.
If you want to delete items I recommend using list comprehension:
x = [1, 2, 3, 4, 5]
x_filtered = [i for i in x if i%2]
Use a new list (comprehension) instead:
x = [1, 2, 3, 4, 5]
y = [item for item in x if not item % 2 == 0]
print(y)
# [1, 3, 5]
Or - considered "more pythonic":
y = [item for item in x if item % 2]
This is because you are removing objects inside of the loop, in other words making the list shorter.
Instead use this:
x = x[0::2]
To select every second value of the list
If you want all the even vaues, instead use a list generator:
x = [value for value in x in value%2 == 0]
You are deleting items from the very list you are iterating over. An alternative approach would be:
x = [1, 2, 3, 4, 5]
answer = [i for i in x if i % 2 != 0]
print(answer)
Outputs:
[1, 3, 5]
x = [1, 2, 3, 4, 5]
for i in range(len(x) -1, -1, -1):
if x[i] % 2 == 0:
x.pop(i)
"range function takes three arguments.
First is the start index which is [length of list – 1], that is, the index of last list element(since index of list elements starts from 0 till length – 1).
Second argument is the index at which to stop iteration.
Third argument is the step size.
Since we need to decrease index by 1 in every iteration, this should be -1." - Source
I highly recommend list comprehension however in certain circumstances there is no point and removing through iteration is fine. Up to you~
use while loop instead of for loop if you want to delete some item.
x = [1, 2, 3, 4, 5]
i = 0
while i<len(x):
if x[i]%2==0:
del x[i]
i+=1
print(x)

Move zeroes to end of list

I am working on moving all zeroes to end of list. .. is this approach bad and computationally expensive?
a = [1, 2, 0, 0, 0, 3, 6]
temp = []
zeros = []
for i in range(len(a)):
if a[i] !=0:
temp.append(a[i])
else:
zeros.append(a[i])
print(temp+zeros)
My Program works but not sure if this is a good approach?
A sorted solution that avoids changing the order of the other elements is:
from operator import not_
sorted(a, key=not_)
or without an import:
sorted(a, key=lambda x: not x) # Or x == 0 for specific numeric test
By making the key a simple boolean, sorted splits it into things that are truthy followed by things that are falsy, and since it's a stable sort, the order of things within each category is the same as the original input.
This looks like a list. Could you just use sort?
a = [1, 2, 0, 0, 0, 3, 6]
a.sort(reverse=True)
a
[6, 3, 2, 1, 0, 0, 0]
To move all the zeroes to the end of the list while preserving the order of all the elements in one traversal, we can keep the count of all the non-zero elements from the beginning and swap it with the next element when a non-zero element is encountered after zeroes.
This can be explained as:
arr = [18, 0, 4, 0, 0, 6]
count = 0
for i in range(len(arr):
if arr[i] != 0:
arr[i], arr[count] = arr[count], arr[i]
count += 1
How the loop works:
when i = 0, arr[i] will be 18, so according to the code it will swap with itself, which doesn't make a difference, and count will be incremented by one. When i=1, it will have no affect as till now the list we have traversed is what we want(zero in the end). When i=4, arr[i]= 4 and arr[count(1)]= 0, so we swap them leaving the list as[18, 4, 0, 0, 0, 6] and count becomes 2 signifying two non-zero elements in the beginning. And then the loop continues.
You can try my solution if you like
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
for num in nums:
if num == 0:
nums.remove(num)
nums.append(num)
I have tried this code in leetcode & my submission got accepted using above code.
Nothing wrong with your approach, really depends on how you want to store the resulting values. Here is a way to do it using list.extend() and list.count() that preserves order of the non-zero elements and results in a single list.
a = [1, 2, 0, 0, 0, 3, 6]
result = [n for n in a if n != 0]
result.extend([0] * a.count(0))
print(result)
# [1, 2, 3, 6, 0, 0, 0]
You can try this
a = [1, 2, 0, 0, 0, 3, 6]
x=[i for i in a if i!=0]
y=[i for i in a if i==0]
x.extend(y)
print(x)
There's nothing wrong with your solution, and you should always pick a solution you understand over a 'clever' one you don't if you have to look after it.
Here's an alternative which never makes a new list and only passes through the list once. It will also preserve the order of the items. If that's not necessary the reverse sort solution is miles better.
def zeros_to_the_back(values):
zeros = 0
for value in values:
if value == 0:
zeros += 1
else:
yield value
yield from (0 for _ in range(zeros))
print(list(
zeros_to_the_back([1, 2, 0, 0, 0, 3, 6])
))
# [1, 2, 3, 6, 0, 0, 0]
This works using a generator which spits out answers one at a time. If we spot a good value we return it immediately, otherwise we just count the zeros and then return a bunch of them at the end.
yield from is Python 3 specific, so if you are using 2, just can replace this with a loop yielding zero over and over.
Numpy solution that preserves the order
import numpy as np
a = np.asarray([1, 2, 0, 0, 0, 3, 6])
# mask is a boolean array that is True where a is equal to 0
mask = (a == 0)
# Take the subset of the array that are zeros
zeros = a[mask]
# Take the subset of the array that are NOT zeros
temp = a[~mask]
# Join the arrays
joint_array = np.concatenate([temp, zeros])
I tried using sorted, which is similar to sort().
a = [1, 2, 0, 0, 0, 3, 6]
sorted(a,reverse=True)
ans:
[6, 3, 2, 1, 0, 0, 0]
from typing import List
def move(A:List[int]):
j=0 # track of nonzero elements
k=-1 # track of zeroes
size=len(A)
for i in range(size):
if A[i]!=0:
A[j]=A[i]
j+=1
elif A[i]==0:
A[k]=0
k-=1
since we have to keep the relative order. when you see nonzero element, place that nonzero into the index of jth.
first_nonzero=A[0] # j=0
second_nonzero=A[1] # j=1
third_nonzero=A[2] # j=2
With k we keep track of 0 elements. In python A[-1] refers to the last element of the array.
first_zero=A[-1] # k=-1
second_zero=A[-2] # k=-2
third_zero= A[-3] # k=-3
a = [4,6,0,6,0,7,0]
a = filter (lambda x : x!= 0, a) + [0]*a.count(0)
[4, 6, 6, 7, 0, 0, 0]

Trying to find the duplicates of a sum of a list

I am attempting to find the first set of duplicates in a list which is the sum of another list (should take a list such as [0,+1,+2,+1,-5,+1,+2] and return 0, because the sum of the list progressively [0,1,3,4,-1,0,2] has its first duplicate at 0). However, it seems to be returning wrong answers no matter what I do. Here's my code:
sourceList = [+11,+16,+2,-16,-6,+13,-6,-8,-17,+15,-11,-14,+17,-9,+4,+1,-15,-17,-8,+16,+6,-11,-15,-2,+3,-6,-2,-4,-2,-18,-6,-2,+18,-4,+7,+15,-11,+8,-4,-17,-19,-15,-17,-7,+12,-2,-5,-13,-4,-13,-18,+16,+12,+10,+14,-19,+13,+10,+8,-10,+3,-16,-7,-9,-19,-15,-19,+2,+7,-1,+7,+1,-20,-5,-10,+14,+10,+5,+13,+10,+15,+3,-16,-17,+19,+15,+2,+14,-11,+3,-11,-17,-8,+16,-13,-1,+16,+1,+12,-5,+20,+16,+10,-9,-8,+15,-4,+3,+5,+7,+1,-2,+19,+14,-5,-10,-4,+3,+18,-15,+17,-7,+10,-8,-10,+4,+10,+12,+11,-4,-4,-8,+11,+16,-4,+14,+2,+3,-16,+19,-9,+5,+17,+19,-17,-4,+13,+15,-6,+20,+12,+15,-17,+13,-7,-14,+11,+12,-10,+16,-10,+8,+11,-8,+16,+5,+3,-4,+15,-16,+4,+2,+5,+8,+12,+7,+8,+4,+19,-14,+2,+8,+12,-14,+19,+15,-14,-17,-5,+7,-20,-5,-4,+14,-21,+4,-8,-7,+14,-18,-7,-18,-13,-19,-8,+16,+3,-8,+14,+10,+9,+18,-15,+14,-1,+18,+15,+17,+11,+16,-11,+7,-13,-14,-6,-11,+13,-12,-2,+9,-12,+10,-18,-6,-8,+9,+3,+6,+11,+15,+15,+5,+19,+15,+8,-12,-17,-8,+19,+2,+9,+2,+6,-10,-22,-9,+19,-5,-4,+3,+14,-1,-20,+15,+17,-1,+19,-8,+11,-13,+17,+9,+9,+18,+7,-8,+11,+16,+9,-1,+15,+19,-25,+1,-6,+8,-4,+13,-23,+15,+3,-14,+1,+25,+51,-11,+5,+5,-13,+17,+13,+16,+17,+17,+18,+16,+16,-5,-20,-19,+15,-14,-15,-6,+9,-1,-5,-13,-24,+13,-18,-29,+3,+10,-14,+29,-21,+11,+13,+24,+23,-17,+19,-1,-7,-5,+20,+5,-11,+3,-20,-13,+5,-12,-8,+4,-23,-31,+18,-50,-27,-16,+15,-21,-26,-11,-46,-11,-12,-5,-1,-6,+3,+19,+7,+33,+9,+11,+14,+32,+36,+15,-92,+22,-19,-18,-8,-24,+28,-72,+15,-19,-34,-4,-11,-4,+17,+14,-26,-23,-18,-10,-1,+39,-5,+19,+16,+26,-25,-40,-11,+19,-11,+1,+7,-55,-21,+73,+503,+843,+54936,+14,-12,+13,-7,-11,+8,+11,+11,+7,+13,+12,+9,+12,+1,+7,-19,-3,-5,-1,-20,+12,+11,-10,-8,-6,+17,+19,+1,+10,-19,+4,+19,+6,+4,+3,+6,+4,+12,+3,+2,-9,+3,+9,-15,-10,-9,-10,+4,+19,+11,+19,+2,+5,+9,-15,+12,+5,+9,+18,-12,-4,+7,+13,-6,+16,+16,+12,-7,+18,+3,+15,-12,+8,-19,+3,+3,+11,-3,+17,-10,+3,-20,+6,-17,+9,+16,+18,+8,-10,-12,+13,+7,-2,-11,+10,-2,+1,-5,+10,+13,-7,+14,+17,-1,-1,+8,-4,+16,-3,+16,+17,+4,+13,-3,+8,-15,-12,+14,+16,-5,-7,+14,-12,-17,-21,-1,-16,+4,+7,-14,-12,+16,-12,-12,-8,-3,-2,-4,+16,-14,+8,+18,-8,-2,-13,+2,+17,+15,+13,+12,-4,+19,-8,-6,+5,-16,+14,-15,-18,+13,+19,-9,+23,-5,-7,+5,+12,+18,-1,+20,-7,-6,-9,-11,+6,-5,+1,+7,+14,-2,+7,+2,+14,-3,-9,+15,-2,+12,+4,-5,+8,+9,-4,+2,+13,-4,-4,-10,+4,-2,+13,-2,-12,+26,+2,+17,+20,+20,+5,-21,+12,-5,-14,+4,+12,+9,-14,-1,+10,-5,-14,-5,-17,-12,-8,+12,+11,-9,+18,+2,-12,+7,+6,-8,+6,-18,-21,-8,+1,+12,-25,-11,+4,-16,-10,+19,+12,-10,-1,-24,-7,+4,+9,-4,-19,+28,+1,+5,-8,-18,+8,-29,-26,-17,+4,-19,-8,-6,+8,+15,+36,-6,-5,-18,+6,-4,-19,-4,-11,+3,-16,-1,+2,+13,+21,-10,+32,+28,+5,+21,-36,-11,-90,-11,+17,-11,+10,+3,+2,+11,-25,-17,-9,-9,+20,-17,-19,-17,-1,+6,-2,+10,-12,+5,-9,+12,+16,+3,-18,+1,+15,-3,-8,+15,+18,+11,+5,-20,-12,+6,-3,-13,+19,-17,+19,-23,+1,-14,+20,+13,+7,+3,-21,-17,-23,-8,-18,+16,+5,+7,-6,-9,-19,-18,+17,+17,-22,+17,+15,-12,-9,-9,+14,-9,-9,+3,-8,-7,-23,+7,+22,+37,+14,+22,-20,-9,-23,+18,-8,-5,+9,-13,+25,+8,-28,+23,-39,-8,-51,-30,+3,-31,+1,+4,+172,+17,+18,-30,+18,+47,+19,-24,-14,+117,-5,+168,+14,-16,-61,+1728,+54439,-8,+15,+5,-2,-1,-12,-3,+10,+15,+10,-14,+15,-8,+11,+10,-12,+11,+12,+17,-10,-6,-19,-19,+18,-12,-3,-7,-11,-13,-10,+9,-13,+9,-15,+16,+2,+16,+13,-17,+19,+16,+16,-10,-12,-7,+5,-15,+14,+19,-12,+17,+8,-18,+17,-4,+1,+6,-13,-5,+23,-19,-8,-13,-31,-12,-3,+5,-3,+11,-1,-20,-8,+5,-2,-2,-18,+1,-19,-5,-3,+13,+15,+10,-8,-14,+16,-14,+18,-11,+6,-10,-4,+5,-15,+8,-17,-7,-12,-12,+10,+6,-12,-17,+1,+8,+17,+17,-11,+19,-18,+1,-14,-2,-15,-6,-1,-16,+1,-3,+6,-18,+1,-17,+8,+19,+18,-5,+12,+6,-15,+19,-8,-16,+18,+15,-6,+1,-17,-10,-4,-112437]
sumList = []
histList = []
for x in sourceList:
print(str(sumList) + " is list a")
print(str(histList) + " is list b")
print(str(sum(sumList)) + " is the sum of list a")
if sum(sumList) in histList:
print(str(sum(sumList)) + " is the answer!")
break
else:
histList.append(sum(sumList))
sumList.append(sourceList[x])
One way to do this would be to use itertools.accumulate to build the list of accumulated sums for each position in the list. We'll add each to a set as we go, checking if it already exists:
from itertools import accumulate
def get_dupe(sourcelist):
exists = set()
for val in accumulate(sourcelist):
if val in exists:
return val
exists.add(val)
return None
get_dupe([0,+1,+2,+1,-5,+1,+2])
# 0
Your sourcelist will return None, because there are no repeated values in the accumulated sums. Counter(accumulate(sourcelist)).most_common(1) has a count of 1.
From what you're doing, it seems like sumList is the "current" list (that is, the portion of sourceList that you've iterated over). If so, you need to append x (and not sourceList[x] as x is not the index but the number in sourceList). Next, you need to append x to sumList before you check histList:
sourceList = [0,+1,+2,+1,-5,+1,+2]
sumList = []
histList = []
for x in sourceList:
sumList.append(x)
print(sumList, histList)
if sum(sumList) in histList:
print(str(sum(sumList)) + " is the answer!")
break
histList.append(sum(sumList))
Output
[0] []
[0, 1] [0]
[0, 1, 2] [0, 1]
[0, 1, 2, 1] [0, 1, 3]
[0, 1, 2, 1, -5] [0, 1, 3, 4]
[0, 1, 2, 1, -5, 1] [0, 1, 3, 4, -1]
0 is the answer!

Python map a value to each i-th sublist's element

I'm trying to do the following in python: given a list of lists and an integer i
input = [[1, 2, 3, 4], [1, 2, 3, 4], [5, 6, 7, 8]]
i = 1
I need to obtain another list which has all 1s for the elements of the i-th list, 0 otherwise
output = [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]
I wrote this code
output = []
for sublist in range(0, len(input)):
for item in range(0, len(input[sublist])):
output.append(1 if sublist == i else 0)
and it obviously works, but since I'm a newbie in python I suppose there's a better 'pythonic' way of doing this.
I thought using map could work, but I can't get the index of the list with it.
Creating extra variable to get index of current element in interation is quite unpythonic. Usual alternative is usage of enumerate built-in function.
Return an enumerate object. sequence must be a sequence, an iterator,
or some other object which supports iteration. The next() method of
the iterator returned by enumerate() returns a tuple containing a
count (from start which defaults to 0) and the values obtained from
iterating over sequence.
You may use list comprehension with double loop inside it for concise one liner:
input_seq = [[1, 2, 3, 4], [1, 2, 3, 4], [5, 6, 7, 8]]
i = 1
o = [1 if idx == i else 0 for idx, l in enumerate(input_seq) for _ in l]
Alternatively,
o = [int(idx == i) for idx, l in enumerate(input_seq) for _ in l]
Underscore is just throwaway name, since in this case we don't care for actual values stored in input sublists.
Here's a 1-liner, but it's not really obvious:
output = [int(j == i) for j, sublist in enumerate(input) for _ in sublist]
Somewhat more obvious:
output = []
for j, sublist in enumerate(input):
output.extend([int(i == j)] * len(sublist))
Then "0 or 1?" is computed only once per sublist, which may or may not be more efficient.

Return lists that do not have 1s

I want to create what I thought was a fairly straightforward function. The function just runs through a list of lists and returns any list that does not have a 1 in all of the list elements following the second element ([2: ]). So given the list of lists [[1, 2, 1, 1, 1, 1], [4, 5, 1, 2, 0.3, 1, 1, 1]] the function would return [4, 5, 1, 2, 0.3, 1, 1, 1]. What I have so far is:
def discover(A):
"""Looks for list that has an element not equal to one.
"""
for i in range(len(A)):
for j in range(len(A[i])):
if A[i][j+2] != 1:
print A[i]
But when I run the function it finds one list but then prints that list over and over again before giving me an IndexError saying the list index is out of range. This seems to be a fairly easy problem but for some reason I'm not getting it. Any help would be really appreciated.
The problem is these two lines:
for j in range(len(A[i])):
if A[i][j+2] != 1:
What'll happen is that you'll eventually get to a point where j is the length of your list, minus 1. But then you're calling j+2 in the below code, and that's guaranteed to create a number longer than your list, giving you the IndexError. You can fix that with:
for j in range(2,len(A[i])):
if A[i][j] != 1:
As for the endless printing, you're nearly there, but you'll want to stop the loop if you find the non-1 element.
if A[i][j] != 1:
print A[i]
break
Alternately, the other answers will give you the same result more easily. But that's where your current errors are coming from.
for list in A:
if 1 not in list[3:]:
print list
even another solution:
lst = [
[1,2,3],
[1,1,1],
[3,4,5],
[3,5,6],
] # +++
def has1(subLst):
return subLst.count(1) == 0
print filter(has1, lst)
This avoids out of range issues.
def discover(A):
results = []
for lst in A:
for i in lst[3:]:
if i != 1:
results.append(lst)
break
return results
In addition to the other answers here, one could also make use of a generator. The yield statement will allow you to skirt establishing a default list to place your results into; you can just specify the condition you're looking for and yield the result.
>>> def discover(lists):
... for l in lists:
... if not [x for x in l[2:] if x != 1]:
... yield l
>>> stuff = [[2, 3, 4, 5, 1, 2], [2, 5, 1, 1, 1, 1, 1]]
>>> results = discover(stuff) #returns <generator object discover at 0x105e3eb90>
>>> results.next()
[2, 5, 1, 1, 1, 1, 1]
>>>
The magic line here being, if not [x for x in l[2:] if x !=1]. It builds a list from l[2:] and checks that any variable in there does not equal 1; if the list has no length, it means there are no non-1 entries in l[2:] and so it yields l.
A query to check if any element (after the second) != 1 would be:
any(x != 1 for x in mylist[3:])
so
def discover(A):
for mylist in A:
if any(x != 1 for x in mylist[3:]):
print mylist

Categories

Resources