如何在Python中从列表中删除重复的字典?

3
我有一个按特定键排序的字典列表。每个字典包含32个元素,列表中有4000多个字典。我需要编写代码来遍历该列表并返回一个新列表,其中所有重复项都被删除。
这些链接中的方法:
- 在列表中删除重复项 - 如何删除列表中的重复项并保持顺序? 不要帮我,因为字典是不可哈希的。
有什么想法吗?如果需要更多信息,请评论并我会添加信息。
编辑:
重复的字典是具有相同的list[dictionary][key]值的任意两个字典。
Ok,这里是一个详细的解释,供需要的人参考。
我有一个像这样的字典列表:
[ {
    "ID" : "0001",
    "Organization" : "SolarUSA",
    "Matchcode" : "SolarUSA, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Timothy Black",
   }, {
    "ID" : "0002",
    "Organization" : "SolarUSA",
    "Matchcode" : "SolarUSA, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Johen Wilheim",
   }, {
    "ID" : "0003",
    "Organization" : "Zapotec",
    "Matchcode" : "Zapotec, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Simeon Yurrigan",
   } ]

在这个列表中,第一个和第二个字典是重复的,因为它们的Matchcodes是相同的。

现在,这个列表将按照以下代码进行排序:

# sort_by is "Matchcode"
def sort( list_to_be_sorted, sort_by ):
    return sorted(list_to_be_sorted, key=lambda k: k[sort_by])

我有一个按Matchcode排序的字典列表。现在我只需要遍历列表,访问list[dictionary][key]并在两个键值匹配时删除重复项。


3
“与'key'相同值的任何词典”是什么意思?你是指每个键都具有相同的值吗?还是在外部指定了某个“键”的相同值? - abarnert
2
看起来你正在使用字典作为一个穷人版的对象。(这没什么问题。)这个问题可能是一个提示,让你应该为这些数据创建一个类。然后你可以定义__eq____hash__等方法,那些其他的答案也会适用。 - Steven Rumbalski
2
你能否提供三个字典的例子,其中两个应被视为重复项?仍然不清楚字典被视为重复项的标准是什么。 - Henry Keiter
1
@MikeGraham:是的,在这种情况下,它们将是重复项,因为键的值(在这种情况下为“3”)是相同的。 - Jacob Bridges
2
@Jacob-IT:这正是我们要求提供 SSCCE 的原因。显然,字典有32个键的事实与您的问题无关;如果它们只有3个键,那么问题仍然是一样的,对吧?因此,请给我们一个版本,其中包含3个具有3个键的简化字典,它比您现在拥有的文本少得多,也更清晰明了。 - abarnert
显示剩余14条评论
8个回答

11

就像你可以使用 tuple 来获取与 list 等效的可哈希对象一样,你也可以使用 frozenset 来获取与 dict 等效的可哈希对象。唯一的诀窍是你需要将 d.items() 传递给构造函数而不是 d

>>> d = {'a': 1, 'b': 2}
>>> s = frozenset(d.items())
>>> hash(s)
-7588994739874264648
>>> dict(s) == d
True

然后你可以使用你已经看到的解决方案中最喜欢的。将它们倒入一个set中,或者如果你需要保留顺序,可以使用OrderedSetunique_everseen配方。例如:

>>> unique_sets = set(frozenset(d.items()) for d in list_of_dicts)
>>> unique_dicts = [dict(s) for s in unique_sets]

或者,保持顺序并使用键值:

>>> sets = (frozenset(d.items()) for d in list_of_dicts)
>>> unique_sets = unique_everseen(sets, key=operator.itemgetter(key))
>>> unique_dicts = [dict(s) for s in unique_sets]

当然,如果您嵌套了列表或字典,则必须进行递归转换,就像处理嵌套列表一样。


unique_everseen期望key是一个函数。>>> unique_sets = unique_everseen(sets, key=operator.itemgetter) - Ullullu

6
使用itertools.groupby()按键值分组您的字典,然后从每个组中取第一个项目。
import itertools

data =[ {
    "ID" : "0001",
    "Organization" : "SolarUSA",
    "Matchcode" : "SolarUSA, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Timothy Black",
   }, {
    "ID" : "0002",
    "Organization" : "SolarUSA",
    "Matchcode" : "SolarUSA, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Johen Wilheim",
   }, {
    "ID" : "0003",
    "Organization" : "Zapotec",
    "Matchcode" : "Zapotec, Something Street, Somewhere State, Whatev Zip",
    "Owner" : "Simeon Yurrigan",
   } ]


print [g.next() for k,g in itertools.groupby(data, lambda x: x['Matchcode'])]

提供结果。
[{'Owner': 'Timothy Black',  
  'Organization': 'SolarUSA', 
  'ID': '0001',  
  'Matchcode': 'SolarUSA, Something Street, Somewhere State, Whatev Zip'},

 {'Owner': 'Simeon Yurrigan', 
  'Organization': 'Zapotec', 
  'ID': '0003', 
  'Matchcode':'Zapotec, Something Street, Somewhere State, Whatev Zip'}]

我相信这就是你要找的内容。
编辑:我更喜欢unique_justseen解决方案,它更短更具描述性。

如果有人遇到属性错误,请将以下代码:print [g.next() for k,g in itertools.groupby(data, lambda x: x['Matchcode'])]更改为:print [next(g) for k,g in itertools.groupby(data, lambda x: x['Matchcode'])] - The Mask

4

这个回答对于现在已经消除歧义的问题来说是不正确的。



但是这段代码不是返回字典中的键列表吗?我需要一个脚本来返回一个字典列表,只去掉重复的。 - Jacob Bridges
@Jacob-IT,unique_values是一个字典的可迭代对象,已经过唯一化处理(其中“重复项”被视为相等的字典)。 - Mike Graham
2
你的 as_values 函数就是 operator.itemgetter(*the_keys) - abarnert
你确定这不再正确吗?我认为 list_of_dicts = list(unique_everseen(list_of_dicts, key=itemgetter(key))) 就是“现在我只需要遍历列表,访问 list[dictionary][key] 并在两个键值匹配时删除重复项”的要求。我还是误读了 OP 吗? - abarnert
@abarnert,那不是同样的答案,那是另一个答案。 - Mike Graham
显示剩余4条评论

1
所以我有一个按Matchcode排序的字典列表。现在我只需要遍历列表,访问list [dictionary] [key]并在两个键值匹配时删除重复项。 我仍然不太确定这是什么意思。听起来你是说它们将始终按照要用于去重的相同键进行排序。如果是这样,您可以使用itertools recipes中的unique_justseen,使用与sort中使用的相同键函数,例如itemgetter(key)。 使用您编辑后的问题中的示例list_of_dicts
>>> list(unique_justseen(list_of_dicts, key=itemgetter('Matchcode')))
[{'ID': '0001',
  'Matchcode': 'SolarUSA, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'SolarUSA',
  'Owner': 'Timothy Black'},
 {'ID': '0003',
  'Matchcode': 'Zapotec, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'Zapotec',
  'Owner': 'Simeon Yurrigan'}]

如果它们按照与我们进行唯一化的键不同的键进行排序,则它们排序的事实并不相关,unique_justseen 将无法工作:
>>> list_of_dicts.sort(key=itemgetter('Owner'))
>>> list(unique_justseen(list_of_dicts, key=itemgetter('Matchcode')))
[{'ID': '0002',
  'Matchcode': 'SolarUSA, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'SolarUSA',
  'Owner': 'Johen Wilheim'},
 {'ID': '0003',
  'Matchcode': 'Zapotec, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'Zapotec',
  'Owner': 'Simeon Yurrigan'},
 {'ID': '0001',
  'Matchcode': 'SolarUSA, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'SolarUSA',
  'Owner': 'Timothy Black'}]

但是你只需要使用unique_everseen这个方法就可以了:
>>> list_of_dicts.sort(key=itemgetter('Owner'))
>>> list(unique_everseen(list_of_dicts, key=itemgetter('Matchcode')))
[{'ID': '0002',
  'Matchcode': 'SolarUSA, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'SolarUSA',
  'Owner': 'Johen Wilheim'},
 {'ID': '0003',
  'Matchcode': 'Zapotec, Something Street, Somewhere State, Whatev Zip',
  'Organization': 'Zapotec',
  'Owner': 'Simeon Yurrigan'}]

(当然,这一次我们得到的是 0002 而不是 0001,因为在按 Owner 排序后,它现在是其 Matchcode 的第一个值,而不是第二个。)

词典不可哈希在这里并不相关,因为配方只是将键函数的结果存储在其集合中,只要在键key处存储的值是可哈希的,一切都很好。


1
现在我们知道如果一个特定的键匹配,两个字典是重复的,那么问题就非常简单了。只需迭代这些字典,跟踪您已经看到的键,并最终从唯一的键中创建一个新列表。
import collections
def get_unique_items(list_of_dicts, key="Matchcode"):
    # Count how many times each key occurs.
    key_count = collections.defaultdict(lambda: 0)
    for d in list_of_dicts:
        key_count[d[key]] += 1

    # Now return a list of only those dicts with a unique key.
    return [d for d in list_of_dicts if key_count[d[key]] == 1]

注意,我在这里使用了一个defaultdict来计算每个键的出现次数(还有其他方法可以做到这一点,但我认为这是最清晰的方法)。我没有使用set来跟踪“已访问”的键的原因是您将得到列表中每个键的一个副本,包括重复的键。这意味着您必须保留第二个set,以跟踪那些真正重复的键(当您遇到它们时已经在“已访问”键中的那些键),以便不包括它们。

另一方面,如果您想要的只是使用给定键的第一个字典,无论后面是否有重复,set方法都可以很好地工作,就像Mike Graham's second answer中所示。


0
seen_values = set()
without_duplicates = []
for d in list_of_dicts:
    value = d[key]
    if value not in seen_values:
        without_duplicates.append(d)
        seen_values.add(value)

0
基本上,你需要类似这样的东西:no_dup(checked_val_extrator, list),其中 no_dup 可以长成这样:
def no_dup(extractor, lst):
   "keeps only first elements encountered for any particular extracted value using =="
   known = set()
   res = []
   for item in lst:
     if extractor(item) in known: continue
     known.add(extractor(item))
     res.append(item)
   return res

-1

我不完全清楚你想要实现什么,但是:

删除所有重复的字典条目

只要你不介意合并所有的字典,

import itertools
dict(itertools.chain(*map(lambda x: x.items(), list_of_dictionaries)))

1
一般来说,如果您的代码使用eval,那么它是错误的。 ;) - Mike Graham
谢谢你的例子,Mike--我改正了。至于eval,我不完全同意你的观点。eval在生产环境中很糟糕,通常不是一个好的实践方法,但对于孤立的内部一次性数据处理操作,它经常是可以接受的。 - jdotjdot

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