tags:

views:

251

answers:

5

What is the most pythonic way of adding the values of two or more tuples to produce a total for each 'column'?

Eg:

>>> a = (10, 20)
>>> b = (40, 50)
>>> c = (1, 3)
>>> ???
(51, 73)

I've so far considered the following:

def sumtuples(*tuples):
    return (sum(v1 for v1,_ in tuples), sum(v2 for _,v2 in tuples))

>>> print sumtuples(a, b, c)
(51, 73)

I'm sure this far from ideal - how can it be improved?

A: 

If your set of tuples is going to be relatively small, your solution is fine. However, if you're going to be working on very large data sets you should consider using reduce as it will only iterate over the list once compared to your original solution which iterates over the list of tuples twice.

>>> a = (10, 20)
>>> b = (40, 50)
>>> c = (1, 3)
>>> values=[a,b,c]
>>> reduce(lambda x,y: (x[0]+y[0],x[1]+y[1]), values,(0,0))
(51, 73)
Mark Roddy
What about Python 3? No reduce exist right.
Selinap
@Selinap: reduce is in Python 3, but it's moved to the "functools" module.
ars
+5  A: 

I guess you could use reduce, though it's debatable whether that's pythonic ..

In [13]: reduce(lambda s, t: (s[0]+t[0], s[1]+t[1]), [a, b, c], (0, 0))
Out[13]: (51, 73)

Here's another way using map and zip:

In [14]: map(sum, zip(a, b, c))
Out[14]: [51, 73]

or, if you're passing your collection of tuples in as a list:

In [15]: tups = [a, b, c]

In [15]: map(sum, zip(*tups))
Out[15]: [51, 73]

and, using a list comprehension instead of map:

In [16]: [sum(z) for z in zip(*tups)]
Out[16]: [51, 73]
ars
What are you using that gives you those prompts -> In [15]: Out[15]:
Chris Cameron
@Chris: ipython: http://ipython.scipy.org/moin/ -- it's great, I can't recommend it enough!
ars
+1  A: 

Not pure Python, but the preferred way if you have SciPy installed:

from scipy import array
a = array((10, 20))
b = array((40, 50))
c = array((1, 3))

print tuple(a+b+c)
wr
+2  A: 

Since we're going crazy,

a = (10, 20)
b = (40, 50)
c = (1, 3)

def sumtuples(*tuples):
   return map(sum, zip(*tuples))

sumtuples(a,b,c)
[51, 73]

Truth is, almost every time I post one of these crazy solutions, the 'naive' method seems to work out faster and more readable...

Chris Cameron
I agree, the solution posted is fine unless OP is going to be working with massive numbers of tuples.
Mark Roddy
I kind of enjoy these "going crazy" threads as a way of learning bits of the language I didn't know. And yeah, readable always wins (or, ought to) when writing code. But, is the use of zip really that crazy? My impression from reading code is that it's fairly idiomatic.
ars
@ars: I guess zip probably is common. I think it's my University indoctrination to Java that makes me think the tuple class should simply have an add() method. Or In a language with operator overloading the + operator should handle this. Of course that would mean subclassing tuple, when map/sum/zip works just fine. But which class should be responsible, the data structure or the class using the data structure? Every book and online resource chooses a different side...On a lighter note, I agree, these going crazy threads are some of my favourites as well.
Chris Cameron
A: 

These solutions all suffer from one of two problems:

  • they only work on exactly two columns; ((1,2,3),(2,3,4),(3,4,5)) doesn't work; or
  • they don't work on an iterator, so generating a billion rows doesn't work (or wastes tons of memory).

Don't get caught up in the "pythonic" buzzword at the expense of not getting a correct answer.

def sum_columns(it):
    result = []
    for row in it:
        if len(result) <= len(row):
            extend_by = len(row) - len(result)
            result.extend([0] * extend_by)

        for idx, val in enumerate(row):
            result[idx] += val

    return result

a = (1, 20)
b = (4, 50)
c = (0, 30, 3)
print sum_columns([a,b,c])

def generate_rows():
    for i in range(1000):
        yield (i, 1, 2)

lst = generate_rows()
print sum_columns(lst)
Glenn Maynard
You might be interested in [itertools.izip_longest](http://docs.python.org/library/itertools.html#itertools.izip_longest). Pythonic **and** correct ;)
Jeffrey Harris