[Tutor] nested "for" loops

pan@uchicago.edu pan@uchicago.edu
Sat May 3 04:37:02 2003


Peter,


>for i in range(1, 21):
>     for j in range(1, 21):
>         for k in range(1, 21):
>             if (i * i) + (j * j) == (k * k):
>                 print "Pythagorean triple: %d, %d, %d" % (i, j, k)



For this sort of operation, you might wanna learn a little bit of the
'list comprehension'...

First of all lets do some tests:

>>> [x for x in range(1,4)]   # build a list using list comprehension
[1, 2, 3]

>>> [x*x for x in range(1,4)]   # another one
[1, 4, 9]

The following is more interesting. You can get ALL the possible 
'cross-pairs' between two lists using just one liner:
 
>>> [(x,y) for x in range(1,4) for y in range(1,4)]
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]

More:

>>> [(x*x,y*y) for x in range(1,4) for y in range(1,4)]
[(1, 1), (1, 4), (1, 9), (4, 1), (4, 4), (4, 9), (9, 1), (9, 4), (9, 9)]

Now add some conditional check:

>>> [(x*x,y*y) for x in range(1,4) for y in range(1,4) if y*y == 9]
[(1, 9), (4, 9), (9, 9)]

Now get the square sums instead:

>>> [x*x+y*y for x in range(1,4) for y in range(1,4) if y*y == 9]
[10, 13, 18]

So, we are getting close to your question, which can actually be solved
in one line:

>>> [(x,y, x*x+y*y) \
.. for x in range(1,21) \
.. for y in range(1,21) \
.. if (x*x+y*y) in [z*z for z in range(1,21)]]
[(3, 4, 25), (4, 3, 25), (5, 12, 169), (6, 8, 100), (8, 6, 100), (8, 15, 289), 
(9, 12, 225), (12, 5, 169), (12, 9, 225), (12, 16, 400), (15, 8, 289), (16, 12, 
400)]
>>> 

It must be harder to comprehend at first, but after you get yourself
familiar with this type of functional programming, the coding life will
be much easier. 

Besides, the list comprehension is MUCH faster than the for loops. 
Comparing the following two functions:

================================== [A] list comprehension

>>> def lc(count):
.. 	rang = range(1,count+1)
.. 	sq = [x*x for x in rang]
.. 	return [(x,y, x*x+y*y) for x in rang for y in rang if (x*x+y*y) in sq]
.. 
>>> lc(5)
[(3, 4, 25), (4, 3, 25)]
>>> lc(10)
[(3, 4, 25), (4, 3, 25), (6, 8, 100), (8, 6, 100)]


================================== [B] for loops

>>> def fl(count):
.. 	rang=range(1, count+1)
.. 	t = []
.. 	for i in rang:
.. 		for j in rang:
.. 			for k in rang:
.. 				if i*i+j*j==k*k:
.. 					t.append((i,j,k*k))
.. 	return t
.. 
>>> fl(5)
[(3, 4, 25), (4, 3, 25)]
>>> fl(10)
[(3, 4, 25), (4, 3, 25), (6, 8, 100), (8, 6, 100)]

==================================

Using the built-in module 'profile' to test their speeds (thx to 
Magnus and Danny for demonstrating its usage so I can show it
here):

>>> profile.run('lc(20)')
         3 function calls in 0.003 CPU seconds

>>> profile.run('fl(20)')
         3 function calls in 0.018 CPU seconds

In your example (count=20) the for-loop function is 6 times
slower than the list-comprehension. 

If the count is larger:

>>> profile.run('lc(500)')
         3 function calls in 21.986 CPU seconds
>>> profile.run('fl(500)')
         3 function calls in 268.913 CPU seconds

See the for-loop approach is at least 10 times slower, which feels
like forever in my computer.

pan