3

Say I have the following list:

result = [{"name": "a", "number": 1},
{"name": "a", "number": 2},
{"name": "b", "number": 1},
{"name": "a", "number": 1}]

Can I turn it into something like:

result = [{"name": "a", "number": 1, "count": 2},
{"name": "a", "number": 2},
{"name": "b", "number": 1}]

I tried using the Count class but I couldn't make it work with dictionaries.

2
  • @timgeb: it could be syntactically correct, if name, number are variables with the right types. Commented Sep 27, 2018 at 11:11
  • 1
    @timgeb edited accordingly Commented Sep 27, 2018 at 11:14

4 Answers 4

3

You can use collections.Counter with list comprehension:

from collections import Counter
[dict(tuple(t) + (('count', c),)) for t, c in Counter(frozenset(d.items()) for d in result).items()]

This returns:

[{'number': 1, 'name': 'a', 'count': 2}, {'number': 2, 'name': 'a', 'count': 1}, {'number': 1, 'name': 'b', 'count': 1}]
Sign up to request clarification or add additional context in comments.

5 Comments

Subtle error here: you need a frozenset since key may not be in order
@OlivierMelançon can you explain? I do not understand the error
@user6251216 in Python 3.6- dict are not ordered, thus if d1 = {'name': 'a', 'number': 1} and d2 = {'name': 'a', 'number': 1} you have d1 == d2, but it might be that tuple(d1.items()) != tuple(d2.items()) due to the order of iteration. In Python 3.6+ the problem becomes that the order matters and thus if d1 = {'name': 'a', 'number': 1} and d2 = {'number': 1, 'name': 'a'} you again have d1 == d2, but tuple(d1.items()) != tuple(d2.items()) due to ordering. See my answer to see how I used frozenset instead of tuple.
Thus the above code does work, but thanks to an implementation detail of the order of iteration of your dicts (they all have the same hash table), not a language feature. This means it might not work if you ever have a dict that had a key deleted at any point or many other cornercases: this would lead to an incredibly hard to track bug, so I recommend you take the habit of using frozenset and not tuple to compare immutable representation of dicts.
@OlivierMelançon Point taken. I've updated my answer accordingly then. Thanks.
1

THis will create a empty dictionary with keys from unique elements in result and initialise all default values to 0.

_dict = dict.fromkeys(set([e["name"] for e in result]), 0)
output: {'b': 0, 'a': 0}

This will count the number of element in result for the keys present in _dict.

[_dict.update({element["name"]: _dict[element["name"]]+1}) for element in result]

Since your "number" is same for whole list,

[{"name": key, "number": 1, "count": _dict[key]}  for key in _dict.keys()]
output:[{'name': 'b', 'number': 1, 'count': 1},
  {'name': 'a', 'number': 1, 'count': 2}]

if number is not same for all the keys, remove all the duplicate dicts from the result list.

no_dups = [i for n, i in enumerate(result) if i not in result[n + 1:]]
output: [{'name': 'b', 'number': 1}, {'name': 'a', 'number': 1}]

create another dict with values as their number :

new = {} 
[new.update({i["name"]: i["number"]}) for i in b]

Repeat the last step, like this

[{"name": key, "number": new[key], "count": _dict[key]}  for key in _dict.keys()]

1 Comment

Can it be more generic, for when I don't have the field names?
0

One way is to use collections.Counter to count dictionaries by ('name', 'number'), then add counts conditional on the count being greater than 1. This can be achieved using a list comprehension:

from collections import Counter
from operator import itemgetter

keys = ('name', 'number')
c = Counter(map(itemgetter(*keys), L))

res = [{**dict(zip(keys, k)), **({'count': v} if v > 1 else {})} \
       for k, v in c.items()]

Result:

[{'count': 2, 'name': 'a', 'number': 1},
 {'name': 'a', 'number': 2},
 {'name': 'b', 'number': 1}]

Comments

0

The Counter class relies on your objects being hashable to count them. Thus, a work-around could be to cast your dictionaries to their immutable equivalents.

The immutable equivalent of a dict is a frozenset (to account for unorderness of dict) of tuple.

You can then count then and form back a list of dictionaries from the counter.

from collections import Counter

result = [{"name": "a", "number": 1},
    {"name": "a", "number": 2},
    {"name": "b", "number": 1},
    {"name": "a", "number": 1}]

frozen_result = map(lambda d: frozenset(d.items()), result)

count = Counter(frozen_result)

new_result = [dict(k, count=v) for k, v in count.items()]

print(new_result)

Output

[{'number': 1, 'name': 'a', 'count': 2},
 {'number': 2, 'name': 'a', 'count': 1},
 {'name': 'b', 'number': 1, 'count': 1}]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.