24

Suppose I have this code:

def f(x):
    return 2*x,x*x

x = range(3)
xlist, ylist = [f(value) for value in x]

How can I neatly get a result like this?

xlist = [0, 2, 4]
ylist = [0, 1, 4]
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Christophe
  • 620
  • 1
  • 5
  • 13
  • A list comprehension should not be used to make two lists. You can, of course, use `zip` to transpose your list of tuples into two lists, but why try to shoe-horn a list comprehension here? Just iterate through your tuples and append to two lists... keep it simple. – juanpa.arrivillaga Feb 21 '17 at 19:43
  • 1
    @juanpa.arrivillaga: I don't know if I agree with that: first of all list comprehension works faster than appending (on one list, indeed when feeding two lists it can be less efficient). Furthermore it is declarative style to use not that much operations with side effects: list comprehension has usually no side effects: one usually writes code that is convenient for *readers*, not *writers*. Furthermore why is this question downvoted? – Willem Van Onsem Feb 21 '17 at 19:59
  • @WillemVanOnsem I didn't downvote it, for the record. But, using a comprehension, *and then* zipping it, will likely *not* be faster, unless you use a generator comprehension so you do it in one pass instead of building the list of tuples into memory first. But then, likely, wrapping a for-loop in a function will likely give you as good or better performance than using the generator (which decreases performance). Also, it's a matter of style and readability but I guess that is subjective. – juanpa.arrivillaga Feb 21 '17 at 20:04
  • @juanpa.arrivillaga, I agree with willem Van Onsem. The intention here is that the code should have a very clear meaning for later readers. The efficiency is a bonus but I feel that the line: xlist, ylist = zip(*[f(value) for value in x]) has a clear functionality, and is more interpretable than a for loop splitting a list. – Christophe Feb 21 '17 at 20:06
  • You really think it is more clear than `for value in x: a,b = f(value); list1.append(a); list2.append(b)`? – juanpa.arrivillaga Feb 21 '17 at 20:07
  • Anyway, my point here is that you will always have to make superfluous intermediate data-structures, especially if you use a list comprehension, but even if you replaced your list comprehension with a generator expression: `zip(*(f(x) for x in values))` because argument unpacking exhausts the iterator... That sort of thing just bugs me, and likely if this isn't a bottleneck it's fine. Perhaps I'm just being too obsessive. But just note that you can do it in one pass with a for-loop, but these other methods involve multiple passes. – juanpa.arrivillaga Feb 21 '17 at 20:13
  • I think it depends whether you are trying to debug or understand the intended function. Perhaps harder to debug but I think that the intended function is more clear, yes. In addition I'm using this function to generate information for figures, so while 4 lines is not significantly worse than 1 line; if it is replicated 4 times (for each subfigure) it uses quite a bit of unnecessary space. – Christophe Feb 21 '17 at 20:14
  • @Christophe well, that's *what functions are for*. – juanpa.arrivillaga Feb 21 '17 at 20:15
  • A function would also take one line to call, per subfigure. And I could move the subfigure generating code into a function but as a style choice I'd rather have one less layer of complexity and a few lines repeated (as long as there aren't too many) – Christophe Feb 21 '17 at 20:18
  • Please do not edit answers into the question. This is **not a discussion forum**. I rolled back the change. – Karl Knechtel Oct 02 '22 at 00:53

6 Answers6

36

Note that return 2*x,x is short for return (2*x,x), i.e. a tuple. Your list comprehension thus generates a list of tuples, not a tuple of lists. The nice thing of zip however is that you can easily use it in reverse with the asterisk:

xlist,ylist = zip(*[f(value) for value in x])
#                 ^ with asterisk

Note that xlist and ylist will be tuples (since zip will be unpacked). If you want them to be lists, you can for instance use:

xlist,ylist = map(list, zip(*[f(value) for value in x]))

which results in:

>>> xlist
[0, 2, 4]
>>> ylist
[0, 1, 4]

Another way to do this is with separate list comprehensions:

xlist = [f(value)[0] for value in x]
ylist = [f(value)[1] for value in x]

Of course, this repeats the work of f, which is inelegant and can be inefficient.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    good call on the edit, I was generating a simple version and didn't test the simple version, I'll update the prompt. – Christophe Feb 21 '17 at 19:38
  • 1
    Good catch on he mapping too. In my case I'm iterating over the return so lists or tuples are both fine but that is good to note. – Christophe Feb 21 '17 at 19:51
7

Let's make this work. The function is fine:

def f(x):
  return 2*x, x*x

But you want to define the range as follows, notice the starting and ending values:

x = range(1, 4)

Also, you have to call the function with the value, not with the list as parameter. And the final trick to unzip the result into two lists, is to simply zip(*lst) the result of the list comprehension:

xlist, ylist = zip(*[f(value) for value in x])

Now the result is as expected:

xlist 
=> [2, 4, 6]
ylist 
=> [1, 4, 9]
Óscar López
  • 232,561
  • 37
  • 312
  • 386
5

Use the build-in function zip(),

def f(x):
  return 2*x, x*x

x = range(1, 4)
xlist, ylist = zip(*[f(value) for value in x])

print(xlist, ylist)
# ((2, 4, 6), (1, 4, 9))
SparkAndShine
  • 17,001
  • 22
  • 90
  • 134
4

Use

zip(*your_list_of_bituples)

Example

demo_list = [(1, 2), (2, 3), (4, 5)]
zip(*demo_list)

Will give

[(1, 2, 4), (2, 3, 5)]
Elmex80s
  • 3,428
  • 1
  • 15
  • 23
  • I didn't even consider zip(*) but that is absolutely the right tool, and it solved my problem. Thank you for the quick reply (I'll accept in a few minutes once it is allowed). – Christophe Feb 21 '17 at 19:31
1

I know it's late but the following gets what you want.

def f(value):
    xlist = []
    ylist = []
    for x, y in [(2*x, x*x) for x in range(value)]:
        xlist.append(x)
        ylist.append(y)
    return xlist, ylist

x = int(input("enter a value: "))
xval, yval = f(x)

print(f"xlist = {xval}\nylist = {yval}")
0
def f(x):
    yield [2*x, x*x]
        
xlist, ylist = zip(*[next(f(x)) for x in range(3)])

print(list(xlist))
print(list(ylist))

using yield...

[0, 2, 4]
[0, 1, 4]

[Program finished]
Subham
  • 397
  • 1
  • 6
  • 14