我在Python 3.5.2中有一个字典列表,我正在尝试重复删除"。所有字典都是唯一的,但有一个特定的密钥我想要重复删除,保持字典具有最多的非空值。
例如,我有以下词典列表:
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
。
我尝试的是创建一个新的密钥,值为"值计数"像这样:
for d in l:
d['val_count'] = len(set([v for v in d.values() if v]))
现在我要坚持的是如何过滤我的ids
密钥值更高的唯一val_count
的dicts列表。
我对其他方法持开放态度,但由于资源限制,我无法将pandas
用于此项目。
预期产出:
l = [{"id":"a", "foo":"bar", "baz":"bat"},
{"id":"b", "foo":"bar", "baz":"bat"}]
答案 0 :(得分:4)
我会使用groupby并从每组中选择第一个:
1)首先按键排序(创建组)和减少空数(您的既定目标):
>>> l2=sorted(l, key=lambda d: (d['id'], -sum(1 for v in d.values() if v)))
2)然后按id
分组,并在排序列表的groupby中将每个迭代器的第一个元素显示为d
:
>>> 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,否则它们具有相同的空值,你可以添加一个枚举装饰器:
>>> 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
)是stable sort,并且序列只会从列表顺序更改基于关键和空白计数。因此,除非您确定需要使用第二个版本,否则请使用第一个版本。
答案 1 :(得分:1)
您可以使用max
:
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]
输出:
[{'foo': 'bar', 'baz': 'bat', 'id': 'a'}, {'foo': 'bar', 'baz': 'bat', 'id': 'b'}]
答案 2 :(得分:1)
如果您愿意使用第三方库,则可以按None
个值排序,然后输入toolz.unique
:
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
,则source code足够小,可以自行实施。
效果基准
我只包含了每个ID只能提供一个结果的解决方案。许多解决方案不适合复制字典。
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]}]
答案 3 :(得分:0)
这是使用列表推导的一种方式,它使用您已经计算过的'val_count'
值:
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']])]
,并提供:
[{'baz': 'bat', 'foo': 'bar', 'id': 'a', 'val_count': 3},
{'baz': 'bat', 'foo': 'bar', 'id': 'b', 'val_count': 3}]
这可以通过将当前词典的'val_count'
与具有相同val_count'
的所有词典的最大&{39; 'id'
进行比较来实现。请注意,在tie的情况下,保留所有具有max 'val_count'
的词典。
以下行应处理关系,仅保留某个'id'
的第一个实例:
final = [x for i, x in enumerate(new) if x['id'] not in {y['id'] for y in new[:i]}]
几乎可以肯定有更有效的方法来解决这个问题,但这至少可以起作用,可能适合您的需求,具体取决于数据集的大小。
答案 4 :(得分:0)
我会这样做:
num = [list(x.values()).count(None) for x in l]
ls = [x for _,x in sorted(zip(num, l), key=lambda z: z[0])]
然后从排序列表(ls
)中保留任意数量的值。
例如,为了仅保留那些具有最高数量的非None
值的词典(所有词典相同数量的非None
s),你可以这样做:
num = [list(x.values()).count(None) for x in l]
ls, ns = zip(*[(x, d) for d, x in sorted(zip(num, l), key=lambda z: z[0])])
top_l = ls[:list(reversed(ns)).index(ns[0])]
编辑:基于@jpp's comment,我更新了我的代码以处理重复的id
密钥。这是更新的代码:
def agn(l):
num = [list(x.values()).count(None) for x in l]
ls, ns = zip(*[(x, d) for d, x in sorted(zip(num, l), key=lambda z: z[0])])
top_l = ls[:list(reversed(ns)).index(ns[0])]
return list(dict((d['id'], d) for d in top_l).values())
我们还使用与@jpp's answer中相同的定义和设置添加时序比较:
In [113]: %timeit tsw(l)
3.9 s ± 60.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [114]: %timeit dawg(l)
7.48 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [115]: %timeit jpp(l)
5.83 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [116]: %timeit agn(l)
4.58 ms ± 86.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
答案 5 :(得分:0)
@ cdc200 ,您可以尝试以下代码。在这里,我使用了字典的概念。
注意»字典被定义为具有唯一键的无序数据项集合。
我使用 OrderedDict()代替 dict()来保留键的顺序。查看这篇不错的小文章OrderedDict in Python - GeeksforGeeks。
import json
from collections import OrderedDict
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]
d = OrderedDict ();
for index, item in enumerate(l):
if item["id"] not in d:
d[item["id"]] =item
else:
nones1, nones2 = 0, 0
for k in item:
if item[k] is None:
nones1 = nones1 + 1
if d[item["id"]][k] is None:
nones2 = nones2 + 1
if nones2 > nones1:
d[item["id"]] = item
l = [dict_item for dict_item in d.values()]
print (l)
"""
{'foo': 'bar', 'id': 'a', 'baz': 'bat'}, {'foo': 'bar', 'id': 'b', 'baz': 'bat'}]
"""
# Pretty printing the above dictionary
print(json.dumps(l, indent=4))
"""
[
{
"foo": "bar",
"id": "a",
"baz": "bat"
},
{
"foo": "bar",
"id": "b",
"baz": "bat"
}
]
"""
感谢。