在Python中统计列表中对象出现的次数

3

假设我有以下列表:

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

可以把它转换成类似这样的形式吗:
result = [{"name": "a", "number": 1, "count": 2},
{"name": "a", "number": 2},
{"name": "b", "number": 1}]

我尝试使用Count类,但无法将其与字典配合使用。


@timgeb:如果“name,number”是具有正确类型的变量,则它可能在语法上是正确的。 - blue_note
1
@timgeb edited accordingly - liorblob
4个回答

3
您可以使用 list comprehension 和 collections.Counter 进行操作:
from collections import Counter
[dict(tuple(t) + (('count', c),)) for t, c in Counter(frozenset(d.items()) for d in result).items()]

这将返回:
[{'number': 1, 'name': 'a', 'count': 2}, {'number': 2, 'name': 'a', 'count': 1}, {'number': 1, 'name': 'b', 'count': 1}]

1
这里有一个微妙的错误:你需要一个frozenset,因为键可能不是按顺序排列的。 - Olivier Melançon
@OlivierMelançon,你能解释一下吗?我不理解这个错误。 - liorblob
1
在Python 3.6中,字典是无序的。因此,如果d1 = {'name': 'a', 'number': 1}d2 = {'name': 'a', 'number': 1},则d1 == d2,但可能由于迭代顺序的不同,tuple(d1.items()) != tuple(d2.items())。在Python 3.6+中,问题在于顺序很重要,因此如果d1 = {'name': 'a', 'number': 1}d2 = {'number': 1, 'name': 'a'},则再次有d1 == d2,但由于顺序不同,tuple(d1.items()) != tuple(d2.items())。请参见我的答案,了解我如何使用frozenset而不是tuple - Olivier Melançon
因此,上述代码确实可以工作,但这要归功于字典迭代顺序的实现细节(它们都有相同的哈希表),而不是语言特性。这意味着如果您曾经有一个在任何时候删除了键或许多其他边角情况的字典,它可能无法正常工作:这将导致难以跟踪的错误,因此我建议您养成使用frozenset而不是元组来比较字典的不可变表示的习惯。 - Olivier Melançon
1
@OlivierMelançon 理解了。我已经相应地更新了我的答案。谢谢。 - blhsing

1
这将创建一个空字典,其键来自结果中的唯一元素,并将所有默认值初始化为0。
_dict = dict.fromkeys(set([e["name"] for e in result]), 0)
output: {'b': 0, 'a': 0}

这将计算结果中字典中存在的键的元素数量。
[_dict.update({element["name"]: _dict[element["name"]]+1}) for element in result]

由于您的“数字”对整个列表都相同,
[{"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}]

如果所有键的数字不相同,则从结果列表中删除所有重复的字典。
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]

重复上一步,就像这样。
[{"name": key, "number": new[key], "count": _dict[key]}  for key in _dict.keys()]

能否更加通用,以便在没有字段名称时使用? - liorblob

0
一种方法是使用collections.Counter来按('name', 'number')计数字典,然后根据计数大于1的条件添加计数。这可以通过列表推导式实现:
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()]

结果:

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

0

Counter 类依赖于对象的可哈希性来计数。因此,一个解决方法是将字典转换为它们的不可变等价物。

dict 的不可变等价物是由 frozenset(考虑到 dict 的无序性)和 tuple 组成的。

然后您可以对其进行计数,并从计数器中重新形成字典列表。

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)

输出

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接