Steven G
Steven G

Reputation: 17132

Add class attributes if another class attribute match the other in a list

I have a class such has:

class ex(object):

    def __init__(self, tag, quantity):
        self.tag = tag
        self.quantity = quantity

I also have a list with multiple instance of this class such has:

[ex('a', 10.5), ex('b', 5.0),  ex('c', 2.0), ex('a', 5.0), ex('c', -10.0)]

I am trying to figure out a nice way to add the self.quantiy attribute if the self.tag matches another instance in the list.

output would be :

[ex('a', 15.5), ex('b', 5.0),  ex('c', -8.0)]

thanks

edit: final instance can be a new instance or the first of the list accumulated. the max size of the list will be around len() of 1000

Upvotes: 1

Views: 172

Answers (3)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96028

Here is an O(n) solution creating new objects:

In [9]: class Ex(object):
   ...:     def __init__(self, tag, quantity):
   ...:         self.tag = tag
   ...:         self.quantity = quantity
   ...:
   ...:     def __repr__(self):
   ...:         return '%s(%r, %r)' % (self.__class__.__name__, self.tag, self.quantity)
   ...:

In [10]: objs = [Ex('a', 10.5), Ex('b', 5.0),  Ex('c', 2.0), Ex('a', 5.0), Ex('c', -10.0)]

In [11]: objs
Out[11]: [Ex('a', 10.5), Ex('b', 5.0), Ex('c', 2.0), Ex('a', 5.0), Ex('c', -10.0)]

In [12]: grouped = {}

In [13]: for obj in objs:
    ...:     grouped[obj.tag] = grouped.get(obj.tag, 0) + obj.quantity
    ...:

In [14]: new_objs = [Ex(k, v) for k,v in grouped.items()]

In [15]: new_objs
Out[15]: [Ex('b', 5.0), Ex('a', 15.5), Ex('c', -8.0)]

You could get fancier using a defaultdict or a Counter, or an OrderedDict if order matters.

Upvotes: 1

wim
wim

Reputation: 362847

I think a plain loop is actually better choice than a list comprehension here. It is possible in O(n) complexity by keeping track of tags already seen:

seen = {}

for instance in in_list:
    try:
        existing = seen[instance.tag]
    except KeyError:
        seen[instance.tag] = instance
    else:
        existing.quantity += instance.quantity

out_list = list(seen.values())  # can drop the "list" call on Python 2.x

The first instance from each group will be used, and updated afterward if the tag is seen again.

If you wish to preserve original ordering of the tags, then drop in a collections.OrderedDict for the seen variable instead of a plain dict. There will be a slight performance penalty, yet the solution remains O(n) complexity.

Upvotes: 1

Alex Hall
Alex Hall

Reputation: 36033

O(n^2), but simpler code.

class ex(object):
    def __init__(self, tag, quantity):
        self.tag = tag
        self.quantity = quantity

    def __repr__(self):
        return '%s(%r, %r)' % (self.__class__.__name__, self.tag, self.quantity)

exes = [ex('a', 10.5), ex('b', 5.0), ex('c', 2.0), ex('a', 5.0), ex('c', -10.0)]
summed_exes = [ex(tag, sum(e.quantity for e in exes if e.tag == tag))
               for tag in sorted({e.tag for e in exes})]
print(summed_exes)

sorted is optional.

Output:

[ex('a', 15.5), ex('b', 5.0), ex('c', -8.0)]

Upvotes: 2

Related Questions