Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

In Python a have a nested for-loop, that populates list with elements:

a = []
for i in range(1, limit+1):
    for j in range(1, limit+1):
        p = i + j + (i**2 + j**2)**0.5
        if p <= limit:
            a.append(p)

I could refactor it into list comprehension:

a = [i + j + (i**2 + j**2)**0.5
     for i in range(1, limit+1)
     for j in range(1, limit+1)
     if i + j + (i**2 + j**2)**0.5 <= limit]

But now the same complex expression is in both parts of it, which is unacceptable. Is there any way to create a list in a functional way, but more elegantly?

I guess, in Lisps i would use recursion with let. How it is done in more functional languages like Clojure, Scala, Haskell?

Edit: In Racket it's possible to bind expressions inside a for/list comprehension. I've found one solution to my problem:

[k
 for i in range(1, limit+1)
 for j in range(1, limit+1)
 for k in [i + j + (i**2 + j**2)**0.5]
 k <= limit]

Not sure, how Pythonic it is.

share|improve this question
    
Just pointing out that you can divide limit by 2 in the range function (since you square the numbers anyway) and you'll still get the full set of results. Also, I would write a transform function that handled the math and run a list comprehension over it. –  kreativitea Dec 19 '12 at 18:56
add comment

2 Answers 2

up vote 2 down vote accepted

In the general case, Jeff's answer is the way to go : generate then filter.

For your particular example, since the expression is increasing wrt both variables, you should stop as soon as you reach the limit.

def max_value(limit, i) :
   """return max positive value one variable can take, knowing the other"""
   if i >= limit :
      return 0
   return int(limit*(limit-2*i)/(2*(limit-i)))

def collect_within_limit(limit) :
   return [ i + j + (i**2 + j**2)**0.5
              for i in range(1,max_value(limit,1)+1)
              for j in range(1,max_value(limit,i)+1) ]

Now, providing this max_value is error prone and quite ad-hoc. We would want to keep the stopping condition based on the computed value. In your imperative solution, adding break when p>limit would do the job. Let's find a functional equivalent :

import itertools

def collect_one_slice(limit,i) :
   return itertools.takewhile(lambda x: x <= limit,
            (i + j + (i**2 + j**2)**0.5 for j in range(1,limit)))

def collect_all(limit) :
   return list(itertools.chain(*(collect_one_slice(limit, i)
                                        for i in range(1,limit))))
share|improve this answer
add comment

You can rewrite it with two separate comprehensions. One to generate the values and one to filter them out. You can use itertools.product to get the cartesian product.

a = [x for x in (i + j + (i**2 + j**2)**0.5
                 for i, j in itertools.product(range(1, limit+1), repeat=2))
     if x <= limit]
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.