如何在Python中对字典列表进行去重

30

我有一个列表:

d = [{'x':1, 'y':2}, {'x':3, 'y':4}, {'x':1, 'y':2}]

如果 {'x':1, 'y':2} 出现了多次,我想将其从列表中删除。我的结果应该是:

 d = [{'x':1, 'y':2}, {'x':3, 'y':4} ]

注意: list(set(d)) 无法在此处正常工作,会导致错误。


4
set()会尝试对给定列表中的每个元素进行哈希。在Python中,dict是不可哈希的,这就是为什么set(d)会引发TypeError错误的原因。 - Rodrigue
2
它总是只有两个元素的字典吗?避免这个问题,使用元组代替。 - Jochen Ritzel
1
可能是重复的问题:Python - List of unique dictionaries - tripleee
1
@tripleee 这不是重复的。你指出的那个使用字典中的单个属性是唯一的。在这种情况下,没有唯一的属性。 - orenma
6个回答

33

如果您的值是可哈希的,那么这将起作用:

>>> [dict(y) for y in set(tuple(x.items()) for x in d)]
[{'y': 4, 'x': 3}, {'y': 2, 'x': 1}]

编辑:

我尝试了去重后,似乎它能正常工作。

>>> d = [{'x':1, 'y':2}, {'x':3, 'y':4}]
>>> [dict(y) for y in set(tuple(x.items()) for x in d)]
[{'y': 4, 'x': 3}, {'y': 2, 'x': 1}]
并且
>>> d = [{'x':1,'y':2}]
>>> [dict(y) for y in set(tuple(x.items()) for x in d)]
[{'y': 2, 'x': 1}]

1
只有在所有字典中保证项目顺序始终一致(即 x 总是在 y 之前)时,此方法才有效。我知道单个字典的顺序是有保证的,但在这种情况下我不太确定。 - Jochen Ritzel
1
x.iteritems() -> tuple(x.iteritems()) 或者你正在比较生成器对象。 - Jochen Ritzel
但如果没有重复元素,那么这会给我错误的结果。 - ramesh.c
@GWW;如果我只尝试匹配“x”键,那怎么样?如果“x”的值匹配,它就是一个重复项。 - ramesh.c
如果字典的值是可变的(例如另一个字典、列表或集合),则此选项将无效。但在这种情况下,它有效,因为所有值都是字符串。 - orenma
显示剩余3条评论

8

字典无法进行哈希操作,因此不能将它们放入集合中。一个相对有效的方法是将键值对转换为元组并对这些元组进行哈希操作(可以自行消除中间变量):

tuples = tuple(set(d.iteritems()) for d in dicts)
unique = set(tuples)
return [dict(pairs) for pairs in unique]

如果值不总是可哈希的,使用集合就无法实现,您可能需要使用每个元素一个 in 检查的 O(n^2) 方法。

1
+1 表示明确指出字典不可哈希,因此使用 set(d) 会产生错误。 - Shawn Chin
类型错误:不可哈希类型:'set'。 - Honarkhah

7
避免这个问题,可以使用namedtuples代替。
from collections import namedtuple

Point = namedtuple('Point','x y'.split())
better_d = [Point(1,2), Point(3,4), Point(1,2)]
print set(better_d)

5
一个简单的循环:
tmp=[]

for i in d:
    if i not in tmp:
        tmp.append(i)        
tmp
[{'x': 1, 'y': 2}, {'x': 3, 'y': 4}]

2
5行代码不如1行(在GWW的回答中),甚至不够易读...这就是为什么适度简洁并不邪恶。 - user395760
3
实际上只有4个(不算印刷品)。 :-) 关于可读性,嗯,简单性在于观察者的眼中。 :-) - Fredrik Pihl

4

如果字典中的某个值看起来像一个列表,则元组的字典将无效。

例如:

data = [
  {'a': 1, 'b': 2},
  {'a': 1, 'b': 2},
  {'a': 2, 'b': 3}
]

使用 [dict(y) for y in set(tuple(x.items()) for x in data)] 可以获取独特的数据。

然而,对这样的数据执行相同的操作将会失败:

data = [
  {'a': 1, 'b': 2, 'c': [1,2]},
  {'a': 1, 'b': 2, 'c': [1,2]},
  {'a': 2, 'b': 3, 'c': [3]}
]

不考虑性能,json dumps/loads 可能是一个不错的选择。

data = set([json.dumps(d) for d in data])
data = [json.loads(d) for d in data]

0
另一个黑魔法(请不要打我):
map(dict, set(map(lambda x: tuple(x.items()), d)))

5
map 并不是黑魔法,它只是一种丑陋的编写列表推导式的方式 ;) 但请注意,在 Python 2 中,这样做非常低效,因为它会创建几个中间列表(如果我数得正确的话是三个),而这些中间列表完全不需要。 - user395760
好的,我同意我们这里有几个不必要的列表。然而,据我所见,上面发布的所有解决方案也没有使用单个列表。但是为什么 map 是列表推导的一种丑陋方式呢? - Artsiom Rudzenka
1
如果你所说的“黑魔法”是指混淆,那么你也可以选择 l=type;y,z,l,o=(map,l({}),set,l(()));y(z,l(o(x.items())for x in d))。与列表推导式相比,嵌套映射通常不太直观易读。 - Shawn Chin
@Artsimon:例如,我的代码使用生成器创建了一个包含n个二元组的元组,使用迭代器创建了一个集合,并创建了一个包含n个字典的列表。你的代码与我的类似,只是在可以使用生成器和迭代器时没有使用它们。 - user395760
1
@delnan:感谢您提供详细信息。我刚开始学习Python(特别是理解迭代器和生成器之间的差异),因此非常感谢任何评论,这对我都很有帮助。 - Artsiom Rudzenka

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