首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >根据另一个键筛选字典列表以删除键中的重复项。

问根据另一个键筛选字典列表以删除键中的重复项。
EN

Stack Overflow用户
提问于 2018-05-21 16:57:32
回答 6查看 2.5K关注 0票数 3

我在Python3.5.2中有一个字典列表,我正在尝试“去复制”。所有的字典都是唯一的,但有一个特定的键我想要去重复,保持字典的非空值最多。

例如,我有以下字典列表:

代码语言:javascript
复制
d1 = {"id":"a", "foo":"bar", "baz":"bat"}
d2 = {"id":"b", "foo":"bar", "baz":None}
d3 = {"id":"a", "foo":"bar", "baz":None}
d4 = {"id":"b", "foo":"bar", "baz":"bat"}
l = [d1, d2, d3, d4]

我想将l过滤到只有唯一的id键的字典中,保留具有最少空值的字典。在这种情况下,函数应该保持d1和d4。

我试图为“值计数”创建一个新的键val对,如下所示:

代码语言:javascript
复制
for d in l:
    d['val_count'] = len(set([v for v in d.values() if v]))

现在,我要坚持的是如何过滤我的字典列表中唯一的ids,其中val_count键是更大的值。

我对其他方法持开放态度,但由于资源限制,我无法在这个项目中使用pandas。

预期产出:

代码语言:javascript
复制
l = [{"id":"a", "foo":"bar", "baz":"bat"},
 {"id":"b", "foo":"bar", "baz":"bat"}]
EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2018-05-22 01:06:08

我会使用群比,从每个组中选择第一个:

1)首先按键对列表进行排序(以创建组)并对空值进行降序计数(您声明的目标):

代码语言:javascript
复制
>>> l2=sorted(l, key=lambda d: (d['id'], -sum(1 for v in d.values() if v))) 

2)然后按id分组,并在排序列表上的groupby中获取每个迭代器的第一个元素作为d:

代码语言:javascript
复制
>>> from itertools import groupby
>>> [next(d) for _,d in groupby(l2, key=lambda _d: _d['id'])]
[{'id': 'a', 'foo': 'bar', 'baz': 'bat'}, {'id': 'b', 'foo': 'bar', 'baz': 'bat'}]

如果您想要一个“领带断路器”来选择第一个dict,否则它们具有相同的空计数,您可以添加一个枚举装饰器:

代码语言:javascript
复制
>>> l2=sorted(enumerate(l), key=lambda t: (t[1]['id'], t[0], -sum(1 for v in t[1].values() if v)))
>>> [next(d)[1] for _,d in groupby(l2, key=lambda t: t[1]['id'])]

但是,我怀疑额外的步骤实际上是必要的,因为Python的排序(和sorted)是一个稳定排序,并且序列只会根据键数和空计数改变列表顺序。所以,使用第一个版本,除非您确定需要使用第二个版本。

票数 4
EN

Stack Overflow用户

发布于 2018-05-21 17:01:19

您可以使用max

代码语言:javascript
复制
d1 = {"id":"a", "foo":"bar", "baz":"bat"}
d2 = {"id":"b", "foo":"bar", "baz":None}
d3 = {"id":"a", "foo":"bar", "baz":None}
d4 = {"id":"b", "foo":"bar", "baz":"bat"}
l = [d1, d2, d3, d4]
max_none = max(sum(c is None for c in i.values()) for i in l)
new_l = [i for i in l if sum(c is None for c in i.values()) < max_none]

输出:

代码语言:javascript
复制
[{'foo': 'bar', 'baz': 'bat', 'id': 'a'}, {'foo': 'bar', 'baz': 'bat', 'id': 'b'}]
票数 1
EN

Stack Overflow用户

发布于 2018-05-22 00:20:31

如果您愿意使用第三方库,则可以按None值的数目进行排序,然后输入toolz.unique。

代码语言:javascript
复制
from toolz import unique
from operator import itemgetter

l_sorted = sorted(l, key=lambda x: sum(v is None for v in x.values()))
res = list(unique(l_sorted, key=itemgetter('id')))

[{'baz': 'bat', 'foo': 'bar', 'id': 'a'},
 {'baz': 'bat', 'foo': 'bar', 'id': 'b'}]

如果您不能使用toolz,那么源代码就足够小,可以自己实现。

性能基准测试

我只包含了每个id都给出一个结果的解决方案。许多解决方案不适合重复的字典。

代码语言:javascript
复制
l = [d1, d2, d3, d4]*1000

%timeit dawg(l)  # 11.4 ms
%timeit jpp(l)   # 7.91 ms
%timeit tsw(l)   # 4.23 s

from operator import itemgetter
from itertools import groupby
from toolz import unique

def dawg(l):
    l2=sorted(enumerate(l), key=lambda t: (t[1]['id'], -sum(1 for v in t[1].values() if v), t[0]))
    return [next(d)[1] for _,d in groupby(l2, key=lambda t: t[1]['id'])]

def jpp(l):
    l_sorted = sorted(l, key=lambda x: sum(v is None for v in x.values()))
    return list(unique(l_sorted, key=itemgetter('id')))

def tsw(l):
    for d in l:
        d['val_count'] = len(set([v for v in d.values() if v]))
    new = [d for d in l if d['val_count'] == max([d_other['val_count'] for d_other in l if d_other['id'] == d['id']])]
    return [x for i, x in enumerate(new) if x['id'] not in {y['id'] for y in new[:i]}]
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50453416

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档