While in a for loop - python

I want to know the number of zeros which first appear in a list, before any other number.
For example:
L1 = [0, 0, 0, 0, 0, 1, 2] - the output should be 5, which is the number of zeros.
L2 = [1, 0, 0, 0, 0, 0, 2] - the output should be zero. Although there are 5 zeros in this list but the list starts with 1.
Here is my code:
k = 0
for i in L1:
while i == 0:
k = k + 1
It doesn't work though. I think it is an infinite loop, but I don't know why.

Think of what will happen the first time i gets set to 0.
The while loop will start and never stop, because i is not changed within that loop.
You would be better off with something as per the following transcript, a slight modification of yours:
>>> list1 = [0,0,0,0,0,1,2]
>>> count = 0
>>> for item in list1:
... if item == 0:
... count = count + 1
... else:
... break
...
>>> print count
5
or the slightly shorter variation which breaks immediately for a non-zero value, adding one otherwise.:
>>> list1 = [0,0,0,0,0,1,2]
>>> count = 0
>>> for item in list1:
... if item != 0: break
... count = count + 1
...
>>> print count
5

As other commentators have said, the problem in your code is that you seem to misunderstand the meaning of the while keyword. That aside, for problems like these, I often prefer a more functional style:
>>> import itertools
>>> k = len(list(itertools.takewhile(lambda x: x == 0, L1)))
>>> k
5
>>> k = len(list(itertools.takewhile(lambda x: x == 0, L2)))
>>> k
0
If you are just beginning to get to know Python, playing around with what the itertools offers is well worth it.

My answer expands on paxdiablo's. Thanks to him for clarifying OP's intent with the L2 case, which I had misread.
While itertools is handy for this kind of thing, at the point you're at, I'd say mastering the basic language features is worthwhile.
In your code, you do this:
L1 = [0, 0, 0, 0, 0, 1, 2]
k = 0
for i in L1:
while i == 0:
k = k + 1
When you run that chunk of code on L1, it loops forever. Here's why:
for causes the code that's enclosed (meaning the code below that's set aside by indentation) to loop. So, your for i in L1: loop runs everything indented below it once for each thing in L1.
But, while does something similar. while tells Python to run the indented code below it over and over until the condition on the while statement is False.
So, in the case of L1, the first time through the for loop, i gets set to 0. Then, while i == 0: says to execute the code enclosed, k = k + 1, over and over again until i is no longer zero. Unfortunately, since the code in the while loop does not change the value of i, i will be zero until the end of time, so it runs forever, repeatedly adding one to k.
What you're looking for is an if statement, which will not loop. It will just run the enclosed code, or not, based on whether its test is true. So, instead of:
while i == 0:
k = k + 1
you can use
if i == 0:
k = k + 1
else:
break
Then, each time through the for loop, the code asks whether i is zero, and if so, only once, it adds one to k, then goes on to the next list element. To cover your L2 case, if you hit a number that is not zero, break exits the for loop and stops counting.
The answer presented with itertools is clever, useful, good to know, and probably what you'd want to use for very large lists, but since it appears you're just learning the language (and probably learning your first language), it's worth learning how to use for, while, and if correctly.

What you're thinking while does, is carry on as normal until the condition isn't met. What it really does, is repeat the code inside of the while as a loop until the condition isn't met.
Here's my solution, which tries to work as similarly to yours as possible, by carrying on counting until the number isn't a 0 then looking at how far it's come and breaking out of the for loop.
k = 0
for index,item in enumerate(L1):
if item != 0:
k = len(L1[:index])
break

Related

How to generate infinite sequence with itertools

I would like to generate two infinitive series of 0 and 1 and specifically in the following order:
0, 1, 0, -1, 0, 1, 0, -1, ...
I have created the following code which does not return what except:
# for in loop
for i in itertools.cycle(range(0,2)):
if i == 0:
i += 1
if i == 1:
i -= 1
if i == 0:
i -= 1
print(i, end = " ")
It just returns a series of -1. Cannot figure out where the error is. Can anyone give any suggestions
You can use itertools.cycle() in a way that explicitly states the elements you want to generate:
from itertools import cycle
cycle([0, 1, 0, -1])
As an alternative, you can implement your own generator with some simple modular arithmetic:
def seq():
i = 0
while True:
yield (-1)**(i // 2) * (i % 2)
i = (i + 1) % 4 # keeps i small, so as not to take up too much memory
Demo:
>>> s = seq()
>>> [next(s) for _ in range(10)]
[0, 1, 0, -1, 0, 1, 0, -1, 0, 1]
I know OP states that they specifically want to use itertools, but this may still be helpful to others.
As pointed out in a comment, you can also simply emulate itertools.cycle by passing arbitrary args and using yield from (note that itertools.cycle actually works differently under the hood):
def seq(*args):
while True:
yield from args
s = seq(0, 1, 0, -1)
But that's not as fun as figuring out the actual arithmetic sequence in my opinion :D
Not sure why you're insisting on using itertools.cycle in a for loop like this, but here's one way you could make this work:
for i in itertools.cycle(range(0, 2)):
if i == 0:
print(i, end=" ")
i += 1
print(i, end=" ")
i -= 1
print(i, end=" ")
i -= 1
print(i, end=" ")
Note that there's really no point in using your nested if statements, since each nested predicate will always be true: if i is zero, and then you add one to it, then of course your nested predicate if i == 1 will be true...
In addition, your use of itertools.cycle in a for loop is an anti-pattern. As shown in BrokenBenchmark's answer, you can simply cycle over the elements of the sequence itself, forever. If you insist on using the for loop:
for i in itertools.cycle([0, 1, 0, -1]):
print(i, end=" ")
There are so many things wrong with this approach though. Namely, as I discussed in the comments, this is an infinite loop which is a fundamentally different behavior than an infinite sequence. With an infinite loop, nothing else can ever happen -- your program will be stuck cycling over your four elements forever with no way to do anything else.
If you use itertools.cycle as it's meant to be used -- as a generator -- you can pick up where you left off in the sequence at any time, allowing you to perform other tasks for as long as you want before continuing your sequence:
c = itertools.cycle((0, 1, 0, -1))
next(c) # 0
next(c) # 1
next(c) # 0
next(c) # -1
# do something else for a while
next(c) # 0
# yield the next 10 items from the sequence
for _ in range(10):
print(next(c), end=" ")
# do something else again

Move a zero from a list to the end and leave the non-zero as is

Problem to solve for: Write an algorithm that takes an array and moves all of the zeros to the end, preserving the order of the other elements.
Solution tested:
array = ["a",0,0,"b","c","d",0,1,0,1,0,3,0,1,9,0,0,0,0,9]
newlist=[]
number_of_zero=0
for i in array:
if i==0:
number_of_zero+=1
if i!=0:
newlist.append(i)
print(newlist)
for i in range(number_of_zero):
newlist.append(0)
print(newlist)
The solution worked great but however when I migrate all this over to a function, my tests fail and I'm not sure why since the solution tested 100% using jupyter notebook
Solution implemented:
def move_zeros(array):
newlist=[]
count = 0
for i in array:
if i==0: #Everytime a zero is encountered, the count of zero is increased
count+=1
if i!=0:
newlist.append(i) #if the value encountered while looping is not zero, append it to the new array
#print(newlist)
for i in range(count):
newlist.append(0) #This loop will append zero times it got counted
return newlist
Can someone suggest me why is my code failing when wrapped into a function? Do not see anything wrong with my logic.
def move_zeros(array):
newlist=[]
zerolist = []
for i in array:
# False == 0 will evaluate to True (but False should stay in place), ignore boolean types
# not isinstance(i, bool) means anything other than True or False
# True and False (like everything else) are objects in python
if not isinstance(i, bool):
# at this point, we can use ==, which will only evaluate to True if an int or float is zero
if i == 0:
# add the zero, but maintain the type (int or float)
zerolist.append(i)
# go to the next iteration
continue
# if the continue statement wasn't executed, everything else will be added here
newlist.append(i)
# append the zero list (with the same types) to the non-zero list
return newlist + zerolist
This looks like a codewars problem I remember solving some time ago. The trick to this problem is accounting for the "truthiness" of 1's and 0's or the "numberness" of True and False.
My solution is you append all the values that are not equal to 0 or are bool values (to capture the False values) using the isinstance() function
then you can calculate the difference in length of your original list against your "non zero" list. Then take that difference and append zeros to it.
def move_zeros(array):
no_zeroes = [x for x in array if x != 0 or isinstance(x, bool)]
solution = no_zeroes + (len(array) - len(no_zeroes))*[0]
return solution
Strip the zeroes and add them all in one go afterwards by counting the difference in length
arr = ["a",0,0,"b","c","d",0,1,0,1,0,3,0,1,9,0,0,0,0,9]
def move_zeros(array):
nz = [e for e in arr if e is not 0]
return nz+[0]*(len(arr)-len(nz))
print(move_zeros(arr))
produces
['a', 'b', 'c', 'd', 1, 1, 3, 1, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Short and simple
newlist = sorted(array, key=(lambda x: x is 0))
If you don't mind mutating the original list you can do
array.sort(key=(lambda x: x is 0))
Note that normally we would test equality with == rather than is, but using is allows us to differentiate 0 from False. This counts on the fact that the CPython implementation preallocates integers from -5 to 256. Unfortunately, that means the behavior of 0 is 0 is implementation defined. You can still count on it though, because there is not a major Python implementation that does not act like CPython in this regard (Cython, PyPy, IronPython, Jython).

Finding a pair of values which add up to given sum

lst = [3, 4, 1, 2, 9]
givenSum = 12
table = {}
x = 0
y = 0
for i in range(0, len(lst)):
table[givenSum - lst[i]] = 1
i += 1
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
break
This is the output
9 and 3 is equal to 12
3 and 9 is equal to 12
I don't know why it's being shown up twice. I need to find a pair of values that add up to the given sum and this is the only way I could think of. I only want it to show up once though any ideas on how I can do that?
There are better solutions, but to fix your issue making minimal changes to your code:
lst = [3, 4, 1, 2, 9]
givenSum = 12
for x in range(0, len(lst) - 1):
for y in range(x + 1, len(lst)):
if lst[x] + lst[y] == givenSum:
print(lst[x], "and", lst[y], "is equal to", givenSum)
break
This will print
3 and 9 is equal to 12
Note that the redundant table is completely removed from the code.
If you run it for a better test case:
lst = [3, 4, 5, 6, 7, 1, 2, 9]
it will print
3 and 9 is equal to 12
5 and 7 is equal to 12
First, to address why the looping continues and gives a second output, break can only break out of its immediate loop. Since you have a nested loop, the break only stops the for y in table: inner loop, but allows for x in table outer loop to move onto it's next iteration. So eventually, x is able to take the value of 3 later on, thus giving you the two outputs you see.
So, if you need a way to stop the iteration entirely when a solution is found, you need to either chain the break statements using a for else syntax (which arguably might be tough to read) as follows,
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
break #breaks from inner loop
else: #for else syntax: this block runs if and only if there was no break encountered during looping.
continue #jumps the outer loop to next iteration
break #this break is set at outer loop's level. Essentially, we can only reach this portion if there is a break in the inner loop.
For else says: run through the whole iteration, and if no break is found, executes the code in the else block. Essentially, the "else" of a "for else" is like a "for - no break".
However, one easier alternative is to use a function with a return (which also makes it easier to read the code).
def find_given_sum(lst, givenSum):
table = {}
x = 0
y = 0
for i in range(0, len(lst)):
table[givenSum - lst[i]] = 1
i += 1
for x in table:
for y in table:
if (x + y) == givenSum:
print(x, "and", y, "is equal to", givenSum)
return #this returns immediately out of the function, thus stopping the iteration.
Also, you could just repeat the break condition, but repeating code is generally not a good practice.
Hope this helps address why the two outputs are being printed. Now, as for the solution itself, there's actually a much better way to solve this. It builds upon the idea of compliments, which you seem to have a sense of in your table. But it doesn't require iteration over the table itself. As a hint: the ideal solution runs in O(n) time. I will not discuss the ideal solution, but hope this prompts you to find the best approach.
Looping twice for n elements costs you O(N^2) time, which is inefficient for large lists. Modified and tested the code to use hash map/dictionary to store the list elements and now it will take only O(N) time.
Map = dict()
lst = [3, 4, 1, 2, 9]
givenSum = 12
for i in range(0, len(lst)):
rem=givenSum-lst[i]
if rem in Map:
print lst[i],lst[Map[rem]]
else:
Map[lst[i]]=i
Store the value of each list element into the map whenever it does not exist in the map.
At each iteration, take the difference between givenSum and current element at that iteration and then search for that difference in the Map.
If it exists it means the pair exists or else not.
In this approach you are running the loop only once, which takes O(N) time because accessing elements in hash map is O(1)/constant time.
Use itertools to get the result
import itertools
sum = 10
lst = [3, 4, 1, 2, 9]
ans = list(filter(lambda x : x[0]+x[1] == sum,itertools.combinations(lst, 2)))

Please explain this python loop which evaluates to an array and is sum()ed

I've seen loops like this a lot on hackerrank but I still don't understand how they work. Why does it have a constant integer '1' in it? Shouldn't it be 'i' instead of '1'? Can anyone please explain this to me.
sum (1 for i in l if i >= a and i <= b)
Credit where credit is due. I copied this loop from a very elegant solution to a problem by Shashwat. The problem was 'Sherlock and Squares' in hackerrank algorithms the for curious ones.
I don't know your values so let's assume:
>>> l = list(range(10))
>>> a = 4
>>> b = 7
If you break down your line of code into a couple of steps and print the intermediate results it's clearer:
>>> [1 for i in l if i >= a and i <= b]
[1, 1, 1, 1]
This is what gets passed to sum. (When you leave off the square brackets it implicitly becomes a generator but this is what it looks like as a list.)
In case you don't understand the comprehension, it's equivalent to this:
>>> result = []
>>> for i in l:
... if i >= a and i <= b:
... result.append(1)
...
>>> result
[1, 1, 1, 1]
The summation would be equivalent to changing result = [] to result = 0 and result.append(1) to result += 1.
In your example they're basically adding 1 to a variable for every item in l if the item is larger than or equal to a and the item is smaller than or equal to b.
This is basically equal to this code:
x = []
for i in l:
if i >=a and i <= b:
x.append(1)
sum(x)
sum (1 for i in l if i >= a and i <= b)
What this is doing, is going to create a generator expression of 1s only if the condition i >= a and i <= b passes while iterating over l and i being your iterator.
Then, sum will add all the 1s together.

Counting down and then up

I'm new to python and trying to run a function that will, given one variable, count down to zero and then up to the original variable. the output should look something like this:
>>> functionname(5)
5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5
so far I've written the code below, but this doesn't count up all the way to the original variable. I guess I need to somehow save the variable in order to refer to it later, but I have no idea how to do that, since python automatically changes n as the function goes on.
def functionname(n):
n = orginal
while n > 0:
print n
n=n-1
print n
if n==0:
print n
n=n+1
I would be very grateful for some pointers, as I seem to be completely stuck at the moment.
Just count from negative to positive and use math:
def fn(n):
print ', '.join(str(abs(i)) for i in range(-n, n+1))
fn(5)
# 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5
Pointers:
If you already know the range you want to iterate over, it's much cleaner and more direct to use a for loop instead of a while.
You can use the range function to generate number sequences.
You can convert simple for loops into list-comprehensions as I did above.
The "simple" clean implementation of your requirements would look something like this:
def fn(n):
for i in range(n, 0, -1):
print i,
for i in range(n + 1):
print i,
Other notes:
range can count backwards too
The end argument to range isn't included in the range itself, that's why the second loop specifies n + 1 as the limit (instead of just n)
Adding a trailing comma on your print makes it add a space at the end instead of a line-break.
Your second block is an if n == 0: (which you know it is since the while loop terminated when n hit 0); presumably you want while n <= 5.
Note that there are nicer ways to accomplish the same thing in Python. For example, using a pair of ranges with itertools.chain to iterate each range one after another allows you to simplify to:
import itertools
def functionname(n):
for i in itertools.chain(range(n, 0, -1), range(n+1)):
print i
Personally, I'd do something like...
def count(n):
for x in range(n, -n, -1):
print(str(abs(x)) + ",")
At the suggestion of dlewin, here's a list comprehension of the same...
def count(n):
print(','.join(str(abs(x)) for x in range(n, -n, -1)))
You need a second while loop that starts at 0 and goes back up to "original".
Do you know about "for" loops yet? Those are better for counting.
Your idea about having original is correct however you are using the assignment operator the wrong way 'round. Also the if n==0 line should be another loop (while or for as suggested by other answers), counting back up to original.
So I'd start with copying the value from n to original like this:
original = n
Hope that helps!
You got some bad formatting there. Remember to indent properly for functions and while and if statements.
So first, set n to 5. Then count down from there until you reach 0 with a while loop:
while n != -1:
print n
n -= 1
Then after the loop breaks, count back up again and reset n to 0:
n = 0
while n < 6:
print n
n += 1

Categories