# newbie question: for loop within for loop confusion

Discussion in 'Python' started by takayuki, Jun 16, 2008.

1. ### takayukiGuest

Hi,

I'm studying python via the exellent book "How to think like a python
programmer" by Allen Downey.

Noob question follows...

animals.txt is a list of animals, each on a separate line: "aardvard,
bat, cat, dog, elephant, fish, giraffe, horse, insect, jackelope"

I want to loop through the list of words and print words that don't
have any "avoid" letter in them.

def hasnolet(avoid):
fin = open('animals.txt')
for line in fin:
word = line.strip()
for letter in avoid:
if letter in word:
break
else:
print word

hasnolet('abcd')

Why doesn't the function above work? It returns:

dog
dog
dog
fish
fish
fish
fish
horse
horse
horse
horse
inchworm
inchworm

thanks for any help.

takayuki

takayuki, Jun 16, 2008

2. ### Roy SmithGuest

In article
<>,
takayuki <> wrote:

> Hi,
>
> I'm studying python via the exellent book "How to think like a python
> programmer" by Allen Downey.
>
> Noob question follows...
>
> animals.txt is a list of animals, each on a separate line: "aardvard,
> bat, cat, dog, elephant, fish, giraffe, horse, insect, jackelope"
>
> I want to loop through the list of words and print words that don't
> have any "avoid" letter in them.
>
> def hasnolet(avoid):
> fin = open('animals.txt')
> for line in fin:
> word = line.strip()
> for letter in avoid:
> if letter in word:
> break
> else:
> print word
>
> hasnolet('abcd')
>

I could give you a fish, or I could teach you to fish. I'd rather do the
latter. So...

> for letter in avoid:
> if letter in word:
> break
> else:
> print word

and instrument it so you can see exactly what's happening. Try something
like:

> for letter in avoid:
> print "letter = %s" % letter
> if letter in word:
> print "it's in word"
> break
> else:
> print "no it's not"
> print word

and see if that helps.

Roy Smith, Jun 16, 2008

3. ### TheSaintGuest

TheSaint, Jun 16, 2008
4. ### John SalernoGuest

takayuki wrote:

> for letter in avoid:
> if letter in word:
> break
> else:
> print word

Take the word 'dog', for example. What the above loop is doing is
basically this:

1. for letter in avoid uses 'a' first
2. is 'a' in 'dog'?
3. no, so it prints 'dog'
4. go back to for loop, use 'b'
5. is 'b' in 'dog'?
6. no, so it prints 'dog' again
7. go back to for loop.....

Since it goes sequentially through 'abcd', it will say that the first
three letters are not in 'dog', and therefore print it three times. Then
it finally sees that 'd' *is* in dog, so it skips it the fourth time
through the loop.

John Salerno, Jun 16, 2008
5. ### John SalernoGuest

takayuki wrote:

> inchworm
> inchworm

P.S. Why does 'inchworm' only print twice? Or is that not the full output?

John Salerno, Jun 16, 2008
6. ### takayukiGuest

Thanks to everyone for the excellent advice.

Roy: I did as you suggested and could see after staring at the output
for awhile what was going on. The print statements really helped to
put a little light on things. Yes, I agree that "learning to fish" is
the best way.

John: There were two "inchworms" because "c" is in "inchworm" so it
shouldn't print. Thanks for your detailed description of the for
loop.

The Saint: i'll check out the word = line.split() command.

After much flailing about, here's a loop that is working:

def hasnolet2(avoid):
fin = open('animals.txt')
for line in fin:
word = line.strip()

length = len(avoid)
x = 0
noprint = 0

while length -1 >= x:
if avoid[x] in word:
noprint = noprint + 1
x = x + 1

if noprint == 0:
print word

hasnolet2('abcd')

which should return:
fish
horse

hasnolet2('abcd')

takayuki, Jun 16, 2008
7. ### Paul HankinGuest

On Jun 16, 2:35 pm, takayuki <> wrote:
> def hasnolet2(avoid):
>         fin = open('animals.txt')
>         for line in fin:
>                 word = line.strip()
>
>         length = len(avoid)
>         x = 0
>         noprint = 0
>
>         while length -1 >= x:
>                 if avoid[x] in word:
>                         noprint = noprint + 1
>                 x = x + 1
>
>         if noprint == 0:
>                 print word

There seems to be an indendation problem (presumably the code from
length = len(avoid) onwards should be inside the loop). But apart from
that, we can try to make this more 'pythonic'.

First, python has a 'for' statement that's usually better than using
while. We use the 'range' function that produces the numbers 0, 1, ...
length - 1, and x takes the value of these in turn.

Here's the last bit of your code rewritten like this:

noprint = 0

for x in range(length):
if avoid[x] in word:
noprint += 1

if noprint == 0:
print word

But better, rather than using 'x' as an index, we can loop over
letters in avoid directly. I've changed 'noprint' to be a boolean
'should_print' too here.

should_print = True
for letter in avoid:
if letter in word:
should_print = False

if should_print:
print word

We can eliminate 'should_print' completely, by using 'break' and
'else'. A break statement in a loop causes the loop to end. If the
loop doesn't break, the 'else' code is run when the loop's over.

for letter in avoid:
if letter in word:
break
else:
print word

This is almost the same as your original code, but the 'else' is
attached to the 'for' rather that the 'if'!

Finally, in Python 2.5 you can write this:

if not any(letter in word for letter in avoid):
print word

I think this is the best solution, as it's readable and short.

--
Paul Hankin

Paul Hankin, Jun 16, 2008
8. ### John SalernoGuest

"takayuki" <> wrote in message
news:...
> John: There were two "inchworms" because "c" is in "inchworm" so it
> shouldn't print. Thanks for your detailed description of the for
> loop.

lol, I even sat there looking at the word and said to myself "ok, it doesn't
contain any of the four letters"

John Salerno, Jun 16, 2008
9. ### John SalernoGuest

"takayuki" <> wrote in message
news:...
> fin = open('animals.txt')
> for line in fin:

You can write this as:

for line in open('animals.txt'):
#do stuff

Of course, you can't explicitly close the file this way, but that probably
doesn't matter. Another way, I think, is to wrap the for loops in this:

with open('animals.txt') as file:
#for loop stuff here

John Salerno, Jun 16, 2008
10. ### MRABGuest

On Jun 16, 7:17 am, Paul Hankin <> wrote:
> On Jun 16, 2:35 pm, takayuki <> wrote:
>
>
>
> > def hasnolet2(avoid):
> > fin = open('animals.txt')
> > for line in fin:
> > word = line.strip()

>
> > length = len(avoid)
> > x = 0
> > noprint = 0

>
> > while length -1 >= x:
> > if avoid[x] in word:
> > noprint = noprint + 1
> > x = x + 1

>
> > if noprint == 0:
> > print word

>
> There seems to be an indendation problem (presumably the code from
> length = len(avoid) onwards should be inside the loop). But apart from
> that, we can try to make this more 'pythonic'.
>
> First, python has a 'for' statement that's usually better than using
> while. We use the 'range' function that produces the numbers 0, 1, ...
> length - 1, and x takes the value of these in turn.
>
> Here's the last bit of your code rewritten like this:
>
> noprint = 0
>
> for x in range(length):
> if avoid[x] in word:
> noprint += 1
>
> if noprint == 0:
> print word
>
> But better, rather than using 'x' as an index, we can loop over
> letters in avoid directly. I've changed 'noprint' to be a boolean
> 'should_print' too here.
>
> should_print = True
> for letter in avoid:
> if letter in word:
> should_print = False
>
> if should_print:
> print word
>
> We can eliminate 'should_print' completely, by using 'break' and
> 'else'. A break statement in a loop causes the loop to end. If the
> loop doesn't break, the 'else' code is run when the loop's over.
>
> for letter in avoid:
> if letter in word:
> break
> else:
> print word
>
> This is almost the same as your original code, but the 'else' is
> attached to the 'for' rather that the 'if'!
>
> Finally, in Python 2.5 you can write this:
>
> if not any(letter in word for letter in avoid):
> print word
>
> I think this is the best solution, as it's readable and short.
>

Alternatively, you could use sets:

if not(set(word) & set(avoid)):
print word

MRAB, Jun 16, 2008
11. ### Thomas HillGuest

On Jun 15, 6:23 pm, takayuki <> wrote:
> def hasnolet(avoid):
> fin = open('animals.txt')
> for line in fin:
> word = line.strip()
> for letter in avoid:
> if letter in word:
> break
> else:
> print word

You're using the split command correctly, but you're not filtering
correctly. Consider this:

---begin---
fin = open('animals.txt')
"\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
len(line)])
----end----

Let's go slow.

"\n".join([...])

1. Take everything that is in the following list, and print each one
with a carriage return appended to it.

"\n".join(["%s" % line for line in fin ...])

2. For each line in fin, create a string that only consists of what
currently in the line variable, using string substitution.

"\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
len(line)])

3. Only do #2 if the length of the line after stripping out the
unnecessary characters is the same length as the line originally. This
way we filter out the lines we don't want. If we wanted the lines that
have been filtered, we can change "==" to "!=" or "<=".

Now, I read "Dive Into Python" first, which through these early on in
the book. If your eyes cross looking at this, write it down and read
it again after you get a little farther into the book you're reading

Thomas Hill, Jun 16, 2008
12. ### Thomas HillGuest

On Jun 16, 2:34 pm, Thomas Hill <> wrote:
> On Jun 15, 6:23 pm, takayuki <> wrote:
>
> > def hasnolet(avoid):
> > fin = open('animals.txt')
> > for line in fin:
> > word = line.strip()
> > for letter in avoid:
> > if letter in word:
> > break
> > else:
> > print word

>
> You're using the split command correctly, but you're not filtering
> correctly. Consider this:
>
> ---begin---
> fin = open('animals.txt')
> "\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
> len(line)])
> ----end----
>
> Let's go slow.
>
> "\n".join([...])
>
> 1. Take everything that is in the following list, and print each one
> with a carriage return appended to it.
>
> "\n".join(["%s" % line for line in fin ...])
>
> 2. For each line in fin, create a string that only consists of what
> currently in the line variable, using string substitution.
>
> "\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
> len(line)])
>
> 3. Only do #2 if the length of the line after stripping out the
> unnecessary characters is the same length as the line originally. This
> way we filter out the lines we don't want. If we wanted the lines that
> have been filtered, we can change "==" to "!=" or "<=".
>
> Now, I read "Dive Into Python" first, which through these early on in
> the book. If your eyes cross looking at this, write it down and read
> it again after you get a little farther into the book you're reading

Guh, no, I'm reading the description of strip wrong. Fooey. Anyone
else able to one line it?

Thomas Hill, Jun 16, 2008
13. ### takayukiGuest

Paul,

Thank you for the informative reply.

Yes, I created the indent problem when manually copying the original
script when I posted. (I'm using an old laptop to study python and
posting here using the desktop.)

Your examples really helped. Last night I played with using a for
loop instead of a while loop and got it working, but your examples
really clarified things.

This loop was particularly interesting because I didn't know the else
could be attached to the for. Attaching it to the for solved a lot of
problems.

for letter in avoid:
if letter in word:
break
else:
print word

I've printed out this whole thread and will be playing with your and
others' solutions.

I love this one for its conciseness, but will have to play with it to

if not any(letter in word for letter in avoid):
print word

Thanks again.

takayuki

takayuki, Jun 17, 2008
14. ### takayukiGuest

On Jun 17, 6:34 am, Thomas Hill <> wrote:
> On Jun 15, 6:23 pm, takayuki <> wrote:
>
> > def hasnolet(avoid):
> > fin = open('animals.txt')
> > for line in fin:
> > word = line.strip()
> > for letter in avoid:
> > if letter in word:
> > break
> > else:
> > print word

>
> You're using the split command correctly, but you're not filtering
> correctly. Consider this:
>
> ---begin---
> fin = open('animals.txt')
> "\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
> len(line)])
> ----end----
>
> Let's go slow.
>
> "\n".join([...])
>
> 1. Take everything that is in the following list, and print each one
> with a carriage return appended to it.
>
> "\n".join(["%s" % line for line in fin ...])
>
> 2. For each line in fin, create a string that only consists of what
> currently in the line variable, using string substitution.
>
> "\n".join(["%s" % line for line in fin if len(line.strip('abcd')) ==
> len(line)])
>
> 3. Only do #2 if the length of the line after stripping out the
> unnecessary characters is the same length as the line originally. This
> way we filter out the lines we don't want. If we wanted the lines that
> have been filtered, we can change "==" to "!=" or "<=".
>
> Now, I read "Dive Into Python" first, which through these early on in
> the book. If your eyes cross looking at this, write it down and read
> it again after you get a little farther into the book you're reading

Thomas,

I'm early on in my python adventure so I'm not there yet on the strip
command nuances. I'm reading "How to think like a python
programmer" first. It's great.

Then "Learning python". I've read parts of Dive into Python and will
work through it fully when I'm a little farther along.

takayuki

takayuki, Jun 17, 2008
15. ### John SalernoGuest

takayuki wrote:

> I'm early on in my python adventure so I'm not there yet on the strip
> command nuances. I'm reading "How to think like a python
> programmer" first. It's great.
>
> Then "Learning python". I've read parts of Dive into Python and will
> work through it fully when I'm a little farther along.

Yeah, I really recommend Learning Python for getting the basics first.
It's very thorough in that regard. Dive Into Python is *not* for
beginners. I'm sorry if people disagree, but it's just not.

John Salerno, Jun 17, 2008
16. ### DavidGuest

takayuki wrote:
> Paul,
>
> Thank you for the informative reply.
>
> Yes, I created the indent problem when manually copying the original
> script when I posted. (I'm using an old laptop to study python and
> posting here using the desktop.)
>
> Your examples really helped. Last night I played with using a for
> loop instead of a while loop and got it working, but your examples
> really clarified things.
>
> This loop was particularly interesting because I didn't know the else
> could be attached to the for. Attaching it to the for solved a lot of
> problems.
>
> for letter in avoid:
> if letter in word:
> break
> else:
> print word
>
>
> I've printed out this whole thread and will be playing with your and
> others' solutions.
>
> I love this one for its conciseness, but will have to play with it to
> get my head around it.
>
> if not any(letter in word for letter in avoid):
> print word
>
> Thanks again.
>
> takayuki
> --
> http://mail.python.org/mailman/listinfo/python-list
>
>
>

takayuki,

Could you post the complete script.
thanks
david

--
http://www.linuxcrazy.com

David, Jun 17, 2008
17. ### Gabriel GenellinaGuest

En Mon, 16 Jun 2008 22:51:30 -0300, John Salerno <> escribió:

> takayuki wrote:
>
>> I'm early on in my python adventure so I'm not there yet on the strip
>> command nuances. I'm reading "How to think like a python
>> programmer" first. It's great.
>>
>> Then "Learning python". I've read parts of Dive into Python and will
>> work through it fully when I'm a little farther along.

>
> Yeah, I really recommend Learning Python for getting the basics first.
> It's very thorough in that regard. Dive Into Python is *not* for
> beginners. I'm sorry if people disagree, but it's just not.

Sure, the author himself says so at the very beginning in http://www.diveintopython.org "Dive Into Python is a Python book for experienced programmers."

--
Gabriel Genellina

Gabriel Genellina, Jun 17, 2008
18. ### John SalernoGuest

Gabriel Genellina wrote:
> En Mon, 16 Jun 2008 22:51:30 -0300, John Salerno <> escribió:
>
>> takayuki wrote:
>>
>>> I'm early on in my python adventure so I'm not there yet on the strip
>>> command nuances. I'm reading "How to think like a python
>>> programmer" first. It's great.
>>>
>>> Then "Learning python". I've read parts of Dive into Python and will
>>> work through it fully when I'm a little farther along.

>> Yeah, I really recommend Learning Python for getting the basics first.
>> It's very thorough in that regard. Dive Into Python is *not* for
>> beginners. I'm sorry if people disagree, but it's just not.

>
> Sure, the author himself says so at the very beginning in http://www.diveintopython.org "Dive Into Python is a Python book for experienced programmers."
>

I know, but I just hear so many people recommend that book for people
who want to learn the language.

John Salerno, Jun 17, 2008