获取唯一字典的列表,同时累积其属性的计数


duplicate_array= [
{'id': 1, 'name': 'john', 'count': 1},
{'id': 1, 'name': 'john', 'count': 2},
{'id': 2, 'name': 'peter', 'count': 1},
]

如何获得唯一字典的列表,在累积重复字典的"计数"的同时删除重复字典?

[
{'id': 1, 'name': 'john', 'count': 3},   //here is main use case that I want to get total count 
{'id': 2, 'name': 'peter', 'count': 1},
]

我试过这得到唯一的值,但我不知道如何积累的结果?

final = list({v['id']:v for v in duplicate_array}.values())

下面是一些不使用任何python库的代码。但是这会导致代码变长。

duplicate_array= [
{'id': 1, 'name': 'john', 'count': 1},
{'id': 1, 'name': 'john', 'count': 2},
{'id': 2, 'name': 'peter', 'count': 1},
]
final=[]
for i, x in enumerate(duplicate_array):
count = 0

for d in duplicate_array.copy():
if d != 0 and d["id"] == x["id"] and d["name"] == x["name"]:
count += d["count"]
duplicate_array.remove(d)

duplicate_array.insert(i, 0)
x["count"] = count
final.append(x)

在第一个代码块中,定义原始列表并初始化输出列表。

然后是for循环

首先,我们将count初始化为0。然后再次循环遍历列表,查找与当前字典具有相同id和名称的所有字典。如果存在,则将count与它们的count值相加,并将它们从列表中删除。我们还检查字典是否为非零,因为稍后要向数组中添加零。这可以防止程序崩溃。

我们在列表的当前位置插入一个0,以防止python跳过下一项。For循环保存一个计数器,用于记录它们在python中所处的项目。然而,当我们删除当前项时(我们在嵌套的for循环中做了),这个计数器将不再匹配正确的项,因为所有next项都向左移动了一位。通过在原始列表中插入一个零,我们将所有项移回,并使索引再次正确。

最后,将原始字典的计数设置为刚才计算的值,并将唯一字典添加到最终列表中。

在这段代码之后,duplicate_array将被填满0。如果你不想这样做,你可以先用duplicate_array.copy()复制列表。

因此,如果您可以使用Pandas,则可以这样做:

import pandas as pd
results = pd.DataFrame(duplicate_array).groupby(["id", "name"]).agg("sum").reset_index().to_dict(orient="records")

缺点是你正在使用一个相当大的库,但我认为这种方式的可读性很好。

最好将其封装在自己的函数中:

def dedupe(dt: list) -> list:
dx = dict() 
for item in dt:
key_id = (item.get('id'), item.get('name'))  # We assume that an id+name is a unique identity
current = dx.get(key_id, {
'id': item.get('id'),
'name': item.get('name'),
}  # get() lets us provide a default value if it doesn't exist yet
current['count'] = current.get('count', 0) + item.get('count', 0)  # update the current count with the count from the new item.
dx[key_id] = current  # Update the result dictionary

return [d for _, d in dx]  # Convert back to a list 
duplicate_array = [
{'id': 1, 'name': 'john', 'count': 1},
{'id': 1, 'name': 'john', 'count': 2},
{'id': 2, 'name': 'peter', 'count': 1},
]
result = dedupe(duplicate_array)

这利用了几个常见的python特性:

  • 可哈希的元组可以用作字典中的键。
  • 我们可以使用get来提供一个默认值,在这种情况下是一个"初始化"值。当我们第一次看到一个唯一键(它不存在于字典中)时,我们提供这个值。然后我们从复制的数组中添加计数。
  • 因为我们使用字典来累积结果,所以我们可以利用唯一键来删除数组的重复项。只需要end是将字典中的值作为新数组。

注意,key_id可以是字典的id,而不是id和名称的组合。这应该在O(2n)O(n)中完成。您只遍历一次初始列表,然后遍历一次结果列表(如果存在重复列表,则结果列表会更小)。如果您愿意使用字典而不是列表,则可以跳过第二步。

另一种方法是分两步,首先获得所有唯一的id,然后累积这些id的所有计数:

st = {(item.get('id'), item.get('name')) for item in duplicate_array}
ls = [{'id': id, 'name': name, 'count': sum(item.get('count') for item in duplicate_array if item.get('id') == id)} for id, name in st]

输出结果:

>>> st = {(item.get('id'), item.get('name')) for item in duplicate_array}
>>> ls = [{'id': id, 'name': name, 'count': sum(item.get('count') for item in duplicate_array if item.get('id') == id)} for id, name in st]
>>> ls
[{'id': 2, 'name': 'peter', 'count': 1}, {'id': 1, 'name': 'john', 'count': 3}]

这更紧凑,但更难解压缩。第一个传递(st = ...)是创建一组元组,类似于第一个选项。第二步是创建一个字典数组,其中每个字典遍历原始数组,查找应该累加到count中的值。

我确实认为这将在非常大的集合中变慢,因为ls =...中的每个新字典创建都会经过整个数组。在最坏的情况下,如果没有副本,这意味着O(n^2)。但如果你想要的是紧凑性,那就对了。

最新更新