2

Is there a way to aggregate the value of each entry of a list using Counter, and not just count the number of time an entry is present ?

For example, if I have a list

from collections import Counter

lst = [
    ['John', 1],
    ['James', 2],
    ['John', 3],
    ['Andy', 1]
]

and I do

total = Counter(name for name, num in lst)
print(total)

The output will be

Counter({'John': 2, 'James': 1, 'Andy': 1})

Which is expected. I was just wondering if there's a way to obtain

Counter({'John': 4, 'James': 2, 'Andy': 1})

I know there are many ways to do it, but I'm looking for a way to do it with Counter.

Luke B
  • 1,143
  • 1
  • 11
  • 22

3 Answers3

2

You can use a simple loop to add the values:

total = Counter()
for name, num in lst:
    total[name] += num

print(total)  # -> Counter({'John': 4, 'James': 2, 'Andy': 1})

If there's a way to do this in one line in O(n), I'd love to know.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • You could use the list comprehension way: [c.update({k:v}) for k,v in lst] where c = Counter(). Just tested it, it works :) – JarroVGIT Jul 29 '20 at 19:11
  • 2
    @Djerro That's still two lines. I mean creating the `Counter` and updating it all in one line. And anyway, [don't use a list comprehension for side effects](https://stackoverflow.com/q/5753597/4518341). – wjandrea Jul 29 '20 at 19:14
2

You need to loop through the list and update the counter accordingly.

c = Counter()

for k, v in lst:
    c.update({k: v})

>>> print(c)
Counter({'John': 4, 'James': 2, 'Andy': 1})

You can also use a list comprehension, but using a comprehension for side effects is not recommended:

[c.update({k: v}) for k, v in lst]
print(c)

Output is the same.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
JarroVGIT
  • 4,291
  • 1
  • 17
  • 29
  • The first method is good, second is not. [Don't a list comprehension for side effects](https://stackoverflow.com/q/5753597/4518341). (I replied to your comment but mentioning it here too for visibility.) – wjandrea Jul 29 '20 at 19:18
  • Yes, fair point on the list comprehension. It felt kinda like a hack when I thought of it, thanks for the link! – JarroVGIT Jul 29 '20 at 19:24
2

Adding another level of for loop to posted list comprehension

total = Counter(name for name, num in lst for _ in range(num))

print(total)
# Output: Counter({'John': 4, 'James': 2, 'Andy': 1})
DarrylG
  • 16,732
  • 2
  • 17
  • 23
  • 1
    I like how it is in one line, but it can become very expensive if the list (and especially the numbers) get very big as it basically gives back the name John 4 times now, rather then update the key John with an additional count of 3. – JarroVGIT Jul 29 '20 at 19:25
  • 2
    @DjerroNeth--agree. In comparison to update counter with each name, value pair this method is (1) slightly faster with just a few elements as in the posted case, (2) comparable with dozens of elements, (3) much slower with hundreds or more elements. This is based upon a timing test. – DarrylG Jul 29 '20 at 19:35
  • Why does it gives back the name John 4 times in a row ? I might be missing something about how it works fundamentally. Thanks for the many answers and the details about the execution time. – Luke B Jul 30 '20 at 13:59
  • 1
    @LukeB--that's because the inner loop `for _ in range(num)`. So `print([name for name, num in lst for _ in range(num)])` outputs `['John', 'James', 'James', 'John', 'John', 'John', 'Andy']`. So Counter(..) of this list provides the desired result. Note: difference is I'm providing a generator to Counter rather than a list, but same result. – DarrylG Jul 30 '20 at 14:11