2

Up to this point in time, in Python, I've only ever seen list comprehensions that specify the inclusion of one element at time. For example, they're all in the following form

[f(x) for x in <iterable>]

Is there any way to specify more than one element at a time? For example,

[f(x), g(x) for x in <iterable>]

I'm asking because I want to create a list comprehension that calculates all the divisors of some number x, but I want to do this as efficiently as possible. Right now I'm using the following,

[y for y in range(1,x+1) if x%y == 0]

but I'd like to do something like this,

[y, x/y for y in range(1, sqrt(x)+1) if x%y == 0]

as this would be more efficient. Btw, I have no practical reason for doing this. It's simply a challenge problem that somebody told me and the goal is to do it with the smallest, most efficient list comprehension possible.

Thanks in advance.

Edit: Ok, it looks like I have to use tuples. I was trying to avoid that though as I'd then have to make another function to flatten the list which would make my solution longer.

Edit 2: Just in case anyone stumbles upon this question, the answer to what I wanted to do in my original question is this:

[y for x in <iterable> for y in f(x), g(x)]

It uses nested for loops in the list comprehension to get the job done.

martega
  • 2,103
  • 2
  • 21
  • 33
  • 1
    From your question and the answers before me, it seems that you should read up on nested list comprehensions. I asked [a similar question](http://stackoverflow.com/questions/3766711/python-advanced-nested-list-comprehension-syntax) some time back, which I think might be helpful to you – inspectorG4dget Jul 18 '12 at 04:58

5 Answers5

5

You can flatten it in place

[y if i else x/y for y in range(1, sqrt(x)+1) for i in 0,1 if x%y == 0]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
3

You can assign to tuples

[(y, x/y) for y in range(1, int(sqrt(x))+1) if x%y == 0]

Not really related to your basic question, but your example: I had to convert the 2nd parameter of range() to an int since sqrt() resulted in a float in my test code.

Update re Edit in post:

To flatten this list of tuples:

In [24]: s
Out[24]: [(1, 20), (2, 10), (4, 5)]

use this:

In [25]: import operator

create a tuple:

In [26]: reduce(operator.add, s, ())
Out[26]: (1, 20, 2, 10, 4, 5)

create a list:

In [27]: list(reduce(operator.add, s, ()))
Out[27]: [1, 20, 2, 10, 4, 5]

Note: In a helpful comment @jamylak points out that reduce and operator.add run order O(N^2), and that using itertools.chain would be much more efficient. This becomes more important as the size of the list grows and should be considered in that case.

Levon
  • 138,105
  • 33
  • 200
  • 191
  • @martega Re your Edit: It's not difficult to flatten the list of tuples, see my updated answer – Levon Jul 18 '12 at 04:19
  • Thanks, I didn't know you could do that :) – martega Jul 18 '12 at 04:24
  • 2
    @Levon I don't like this use of `reduce` and `operator.add`. This is O(N^2), `itertools.chain.from_iterable` would be much more efficient. – jamylak Jul 18 '12 at 05:07
  • @jamylak reduce/add came to mind first, and I bet most of the time these lists will be short enough it wouldn't matter. However I see your point, and if it's ok with you I'll add your comment to the answer so that others reading it will consider it too (since not everyone reads all the comments) – Levon Jul 18 '12 at 05:11
  • @Levon of course that is fine, you don't even need my consent! :D – jamylak Jul 18 '12 at 05:13
3

Yes, you can do it. You just need to put parentheses around the element. What you get is a list of tuples.

>>> [(x, x+1) for x in range(5)]
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
2

Oh so close:

[(y, x/y) for y in range(1, sqrt(x)+1) if x%y == 0]

It is possible to generate a list of tuples, and these of course can hold multiple values.

dimo414
  • 47,227
  • 18
  • 148
  • 244
1

Yes absolutely, just use parenthesis around the value:

[(y, x/y) for y in range(1, sqrt(x)+1) if x%y == 0]
bradley.ayers
  • 37,165
  • 14
  • 93
  • 99