How to change this for loop to while loop? - python

Having a bit of trouble with while loops. I understand this basic for loop runs through whatever is passed into the function but how would I change the for loop to a while loop? Thought it would be as easy as changing the for to while but apparently not so.
def print_data(items):
for item in items:
print(item)

You can do something like this to have the same printing functionality with a while loop:
def print_data(items):
i = 0
while i < len(items):
print items[i]
i += 1

Here is a while loop version that works by constructing an iterator and manually walking it. It works regardless of whether the input is a generator or a list.
def print_data(items):
it = iter(items)
while True:
try:
print next(it)
except StopIteration:
break
print_data([1,2,3])

One option, that works on both lists and generators, is to construct an iterator and then use Python's built-in next function. When the iterator reaches the end, the next function will raise a StopIteration exception, which you can use to break the loop:
def print_data(items):
it = iter(items)
while True:
try:
print next(it)
except StopIteration:
break
print_data(['a', 'b', 'c'])
print_data(('a', 'b', 'c'))
There's more about the next built-in function and iterators in the docs.

If you are learning Python:
If you need to iterate over an iterable (a list, a generator, a string etc.. in short and not precise words something that contains things and can "give" those things one by one..) you better use for.
In Python for was made for iterables, so you don't need a while.
If you are learning programming in general:
while needs a condition to be satisfied to keep looping, you create your own condition making a counter that will increment every loop, and making the while loop go while this counter is less or equal to the lenght of your items, as showed in Mathias711's answer.

The for-loop you are using is iterating through a so called iterator.
This means to walk through iterable objects (lists, generators, dicts,...) and return the next item from the iterator which is returned by the built-in function [next()][2]. If there is no item left, calling this function will raise an so called StopIteration error which causes to stop iteration.
So the pythonic way to iterate througth iteratable objects is in fact using the for-loop you provided in your question. However, if you really want to use a while loop (which at least in general is not recommended at all) you have to iterate using a try-except-block and handle the StopIteration error raised if no item is left.
def iterate_manually(items):
# convert items list into iterator
iterator = iter(items)
while True:
try:
print(next(iterator))
# handle StopIteration error and exit while-loop
except StopIteration:
break
iterate_manually(['foo', 'bar', 'baz'])

You can try this
def print_data(items):
i =0
while items:
if i < len(items):
print items[i]
i = i+1
else:
break

Related

Infinite for loop in Python - is the list updated or not?

First of all, I am aware that I should use while loop to create an infinite one. Nevertheless, while playing with making an infinite for loop in Python I run into something I do not fully understand.
My idea for an "infinite" for loop was following:
l = [1]
for el in l:
print(len(l))
l.append(1)
and this in fact created an infinite loop as 1 was constantly appended to the list l at each loop iteration. So I thought I do not want my list to become longer and longer so that at some point I have not enough memory. So I did this:
l = [1]
for el in l:
print(len(l))
l.pop(0)
l.append(1)
and I got just one iteration. Can someone explain why? Is it because the l is still referenced to the same object and its length is always 1?
The for statement returns an "iterator" which is a container for a stream of data that returns one element at a time, so you are not iterating over the iterable (your list in this case) but instead over this container.
You can return this iterator directly using iter() in order to better understand what is happening with your for loop.
items = ['a']
it = iter(items)
print(it.__reduce__())
# (<built-in function iter>, (['a'],), 0)
The built in __reduce__() function returns state information about the iterator. Note that the state information returned immediately after the iterator is created includes the iterable, ['a'], and an index value of 0. The index value indicates the position of the next element that will be returned by the iterator.
To simulate the first iteration of the loop, we can use next() which returns the next element from an iterator.
next(it)
print(it.__reduce__())
# (<built-in function iter>, (['a'],), 1)
Now we see that the index value has changed to 1 because the iterator already returned the first element in the list and on the next iteration it will attempt to return the second element in the list. If you attempt to remove the first element in the list and then append another element to the list, following is the resulting state of the iterator.
items.pop(0)
items.append('b')
print(it.__reduce__())
# (<built-in function iter>, (['b'],), 1)
You can see that the first element was removed and the new element was appended (as expected). However, the iterator still retained an index value of 1 as the position of the next element to be returned from iteration. If we attempt another iteration, a StopIteration exception will be raised because there is no element at index 1 in the iterable being used by our iterator container.
next(it)
# Traceback (most recent call last):
# File "main.py", line 16, in <module>
# next(it)
# StopIteration
If you are really interested in creating an infinite for loop, using a generator would be a better way to deal with your memory concerns (although as you note in your question, there aren't too many good reasons not to use while for this sort of thing). See my answer to a related question for an example of an infinite for loop.
If you use an iterator as suggested above, it makes it way easier to implemented.
The class below implements the ___iter___ and ___next___ method needed for a iterator classes
class WhileLoop:
def __init__(self, value):
#Assign the value
self.value = value
def __iter__(self):
#The iterator will be the class itself
return self
def __next__(self):
#The next element in the loop is the value itself
return self.value
You can then call and loop on this iterator as follows.
loop = WhileLoop(1)
for item in loop:
print(item)

Why must I use a variable to get values from a Python generator?

Why must I use a variable to obtain next() values from a Python generator?
def my_gen():
i = 0
while i < 4:
yield 2 * i
i += 1
#p = my_gen()
#for i in range(4):
# print(next(p))
##for i in range(4):
## print(next(my_gen()))
In the above, the # block works, while the ## block returns 4 copies of the first "yield."
print(next(my_gen()))
Each time this runs, you're calling next() on a separate (and brand-new) generator returned by my_gen().
If you want to call next() on the same generator more than once, you'll need some way to keep that generator around for long enough to reuse it.
just gonna add my $0.02...
this may help clear up some misconceptions:
def function_that_creates_a_generator(n):
while n>0:
yield n
n - 1
# this
for x in function_that_creates_a_generator(5):
print(x)
#is almost exactly the same as this
generator = function_that_creates_a_generator(5)
while True:
try:
x = generator.next() #same as: x = next(generator)
except StopIteration:
break
print(x)
For loops are really just a prettier way of writing a while loop that continually asks an iterable object for its next element. when you ask an iterable for another object and it has none, it will raise a StopIteration exception that is automatically caught in for loops; signaling a break from the loop. In the while loop we simply break these things out individually instead of having them hidden by the interpreter.

Pythonic way to skip items in an iterable?

Say I have an iterable and want to skip elements as long as the elements match a particular predicate. I want to affect the current iterator, not return a new one.
I could simply do this:
# untested, just for explanation
e = next(iterable)
while True:
if something(e):
e = next(iterable)
else:
break
But is there a built-in function for this, or some common idiom?
This is the basic use-case of itertools.dropwhile.
Why not just
e = next(iterable)
while something(e):
e = next(iterable)
Also, why "I want to affect the current iterator, not return a new one."? I can't think of a scenario when you would need that. If you allow for a new iterator to wrap the current one, itertools.dropwhile that wim suggests is much more Pythonic (and readable).
Your example just plain stops after the first false result, so this does the same:
>>> it = iter(range(100000)) # just constructing _an_ iterator
>>> next(itertools.filterfalse(lambda i: i != 12, it))
12
I'm not clear on what - exactly - "I want to affect the current iterator, not return a new one" means, but do note that it itself has advanced:
>>> next(it)
13

Idiomatic list of all natural numbers

I am trying to create a generator that will return the natural numbers in order. This is used to enumerate another generator which will exit upon StopIteration, which seems like the easiest way to do it. However, I cannot find an idiomatic way of creating this generator:
def numbers():
i = 0
while True:
yield i
i += 1
q = Queue.Queue()
for i in numbers():
try:
q.put((i, my_generator.next()))
except StopIteration:
break
This does work, but it seems unpythonic to use a while True: in this way.
Is there a standard library function to iterate over the natural numbers?
To answer your literal question, use itertools.count(). It'll count up from a start value by step, into infinity.
That said, it seems what you actually want to do is this:
for idx, item in enumerate(my_generator):
q.put((idx, item))
You have a couple easy options.
Use a large range():
q = Queue.Queue()
for i in range(10**10):
try:
q.put((i, my_generator.next()))
except StopIteration:
break
Or simply enumerate() the generator you're actually interested in:
q = Queue.Queue()
for item in enumerate(my_generator):
q.put(item)

python find the num in list greater than left and right

The question is use iterator to find the num from list who is greater than the nums in left and right
For example, select([0,1,0,2,0,3,0,4,0]) return [1,2,3,4]
My first try is
def select(iterable):
answer = []
it = iter(iterable)
try:
v2 = next(it)
while True:
v1,v2= v2,next(it)
if v2>v1 and v2>next(it):
answer.append(v2)
except StopIteration:
pass
return answer
This code fails.
I think the next(it) in the while loop would be the same iterator,but the next() still iter next one in the code.
then I change the code to below one, it works.
try:
v1,v2,v3 = next(it),next(it),next(it)
while True:
if v2>v1 and v2>v3:
answer.append(v2)
v1,v2,v3 = v2,v3,next(it)
except StopIteration:
pass
Can someone explain what is difference happen here?
There are two issues with the first snippet:
Every time you call next(it), it advances the iterator. You have to store the returned value if you want to access it more than once. Calling next(it) again won't do that; it will advance the iterator yet again.
Even if the first point weren't an issue, the following would still be problematic:
if v2>v1 and v2>next(it):
The issue here is short-circuit evaluation. Depending on whether v2>v1, this may or may not advance the iterator.
The error lies in this line:
if v2>v1 and v2>next(it):
Your calling next(it), but you don't store the return value. You just compare it to the v2. So the value gets skipped.
edit:
Btw, if you compare multiple values, the following comparison is much more cleaner:
if v1 < v2 > v3:

Categories