1

I want to sum multiple attributes at a time in a single loop:

class Some(object):
    def __init__(self, acounter, bcounter):
        self.acounter = acounter
        self.bcounter = bcounter

someList = [Some(x, x) for x in range(10)]

Can I do something simpler and faster than it?

atotal = sum([x.acounter for x in someList])
btotal = sum([x.bcounter for x in someList])
martineau
  • 119,623
  • 25
  • 170
  • 301
Chameleon
  • 9,722
  • 16
  • 65
  • 127

4 Answers4

6

First off - sum doesn't need a list - you can use a generator expression instead:

atotal = sum(x.acounter for x in someList)

You could write a helper function to do the search of the list once but look up each attribute in turn per item, eg:

def multisum(iterable, *attributes, **kwargs):
    sums = dict.fromkeys(attributes, kwargs.get('start', 0))
    for it in iterable:
        for attr in attributes:
            sums[attr] += getattr(it, attr)
    return sums

counts = multisum(someList, 'acounter', 'bcounter')
# {'bcounter': 45, 'acounter': 45}
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
3

Another alternative (which may not be faster) is to overload the addition operator for your class:

class Some(object):
  def __init__(self, acounter, bcounter):
    self.acounter = acounter
    self.bcounter = bcounter

  def __add__(self, other):
    if isinstance(other, self.__class__):
      return Some(self.acounter+other.acounter, self.bcounter+other.bcounter)
    elif isinstance(other, int):
      return self
    else:
      raise TypeError("useful message")

  __radd__ = __add__

somelist = [Some(x, x) for x in range(10)]

combined = sum(somelist)
print combined.acounter
print combined.bcounter

This way sum returns a Some object.

ilent2
  • 5,171
  • 3
  • 21
  • 30
2

I doubt that this is really faster, but you can do it like thus:

First define padd (for "pair add") via:

def padd(p1,p2): 
    return (p1[0]+p2[0],p1[1]+p2[1])

For example, padd((1,4), (5,10)) = (6,14)

Then use reduce:

atotal, btotal = reduce(padd, ((x.acounter,x.bcounter) for x in someList))

in Python 3 you need to import reduce from functools but IIRC it can be used directly in Python 2.

On edit: For more than 2 attributes you can replace padd by vadd ("vector add") which can handle tuples of arbitrary dimensions:

def vadd(v1,v2):
    return tuple(x+y for x,y in zip(v1,v2))

For just 2 attributes it is probably more efficient to hard-wire in the dimension since there is less function-call overhead.

John Coleman
  • 51,337
  • 7
  • 54
  • 119
0

Use this line to accumulate all of the attributes that you wish to sum.

>>> A = ((s.acounter,s.bcounter) for s in someList)

Then use this trick from https://stackoverflow.com/a/19343/47078 to make separate lists of each attribute by themselves.

>>> [sum(x) for x in zip(*A)]
[45, 45]

You can obviously combine the lines, but I thought breaking it apart would be easier to follow here.

And based on this answer, you can make it much more readable by defining an unzip(iterable) method.

def unzip(iterable):
    return zip(*iterable)
[sum(x) for x in unzip((s.acounter,s.bcounter) for s in someList)]
Community
  • 1
  • 1
Harvey
  • 5,703
  • 1
  • 32
  • 41