Python - 从另一个中减去一个dicts列表

时间:2012-08-23 22:11:45

标签: python list dictionary

我对比较多个列表感兴趣,采取差异并迭代它。

两者都是包含以下键的dicts列表: 'ssid' - str,'bssid' - str,'channel' - int,'flags' - list,'found' - bool

我试过了:

 list = list(set(networks_list).difference(missing_networks))

但我收到错误:

unhashable type 'dict'

我的数据结构如下:

list: [{'found': False, 'flags': ['WPA2-PSK-CCMP', 'WPS', 'ESS'], 'ssid': 'SOHO_BROADCAST', 'bssid': '30:46:9a:9d:11:1a', 'channel': 1}, {'found': False, 'flags': ['WPA-EAP-TKIP', 'WPA2-EAP-CCMP', 'ESS'], 'ssid': 'Cisco 2.4ghz', 'bssid': '40:f4:ec:7f:3c:5a', 'channel': 11}, {'found': False, 'flags': ['WPA-EAP-TKIP', 'WPA2-EAP-CCMP', 'ESS'], 'ssid': 'Cisco 5.0ghz', 'bssid': '40:f4:ec:7f:3c:54', 'channel': 149}]

缺少的网络最初是空的。

有一种简单的方法吗?

9 个答案:

答案 0 :(得分:4)

不是使它们成为dicts列表,而是使它们成为实现__eq____hash__的对象列表,并且您提供的代码应该可以工作

答案 1 :(得分:4)

像这样的通用方法可能存在很多陷阱,但如果你的词典主要是原始的,而不是巨大的,你可以这样做:

假设您的数据如下所示:

networks = [
        {'address': '192.168.1.1'},
        {'address': '127.0.0.1'},
    ]

missing = [
        {'address': '127.0.0.1'}
    ]

您可以将字典列表转换为列表元组(可以清除)

def make_hashable(d):
    return (frozenset(x.iteritems()) for x in d)

networks_hashable = make_hashable(networks)
missing_hashable = make_hashable(missing)

然后减去

diff = set(networks_hashable).difference(missing_hashable)

现在你有一个元组列表

print list(diff)

或转换回词典

print [dict(x) for x in diff]

<强>更新

我已根据@ gnibbler的评论更改了make_hashable的定义。

答案 2 :(得分:2)

不,一般而言,高效率的工作非常困难。但是,您不必解决一般情况,只是针对您尚未详细说明的特定数据结构。

例如,如果你的dict键都是intstr,那么它比键是复数等要容易得多。

编辑: 既然您现在告诉我们您的数据结构,我可以告诉您,一种简单的方法是将dicts转换为 nametuples

注意:您不能将dict转换为带有tuple(dict.items()的元组,因为键的顺序可以从一个dict到下一个dict不同

>>> d = dict(ssid="ssid", bssid="bssid", channel=1, flags="flags", found="True")
>>> networks_list = [d, ]
>>> from collections import namedtuple
>>> NT = namedtuple("my_struct", d.keys())
>>> set(NT(**i) for i in networks_list)
set([my_struct(found='True', flags='flags', channel=1, bssid='bssid', ssid='ssid')])

答案 3 :(得分:2)

dict是一个可变项。这意味着它在其生命周期中没有恒定的哈希值,并且不能被置于集合中。

如果您将所有dicts转换为具有相同功能的字符串,它们将变为可清除,您可以在一组中使用它们...

答案 4 :(得分:2)

如果你尝试一些简单的事情怎么办?

 lst = list(set(networks_list.items()).difference(set(missing_networks.items())))

(顺便说一句:我已经在这里更改了名为 lst 的变量;将某些结果绑定到名称“list”可能是一个坏主意,因为Python支持list()函数。它不是一个关键字,所以它不会抛出异常,但是当你编写一些试图稍后调用list()函数的代码时,你可能会稍后绊倒它。

答案 5 :(得分:2)

这种方法有效:

>>> import random
>>> items = [{'ssid': 'foo%s' % i, 'bssid': 'bar%s' % i, 'channel': i, 'flags': 'abc%s' % i, 'found': random.choice([True, False])} for i in range(1, 11)]
>>> items1 = random.sample(items, 7)
>>> items2 = random.sample(items, 5)
>>> print "\n".join(map(str, items1))
{'found': True, 'flags': 'abc9', 'ssid': 'foo9', 'bssid': 'bar9', 'channel': 9}
{'found': True, 'flags': 'abc7', 'ssid': 'foo7', 'bssid': 'bar7', 'channel': 7}
{'found': False, 'flags': 'abc10', 'ssid': 'foo10', 'bssid': 'bar10', 'channel': 10}
{'found': True, 'flags': 'abc5', 'ssid': 'foo5', 'bssid': 'bar5', 'channel': 5}
{'found': False, 'flags': 'abc4', 'ssid': 'foo4', 'bssid': 'bar4', 'channel': 4}
{'found': True, 'flags': 'abc3', 'ssid': 'foo3', 'bssid': 'bar3', 'channel': 3}
{'found': True, 'flags': 'abc2', 'ssid': 'foo2', 'bssid': 'bar2', 'channel': 2}
>>> print "\n".join(map(str, items2))
{'found': True, 'flags': 'abc3', 'ssid': 'foo3', 'bssid': 'bar3', 'channel': 3}
{'found': True, 'flags': 'abc9', 'ssid': 'foo9', 'bssid': 'bar9', 'channel': 9}
{'found': False, 'flags': 'abc1', 'ssid': 'foo1', 'bssid': 'bar1', 'channel': 1}
{'found': False, 'flags': 'abc8', 'ssid': 'foo8', 'bssid': 'bar8', 'channel': 8}
{'found': True, 'flags': 'abc5', 'ssid': 'foo5', 'bssid': 'bar5', 'channel': 5}
>>> print "\n".join(map(str, [dict(itemset) for itemset in set([tuple(sorted(grp.items())) for grp in items1]).difference([tuple(sorted(grp.items())) for grp in items2])]))
{'found': False, 'flags': 'abc10', 'ssid': 'foo10', 'bssid': 'bar10', 'channel': 10}
{'found': False, 'flags': 'abc4', 'ssid': 'foo4', 'bssid': 'bar4', 'channel': 4}
{'found': True, 'flags': 'abc7', 'ssid': 'foo7', 'bssid': 'bar7', 'channel': 7}
{'found': True, 'flags': 'abc2', 'ssid': 'foo2', 'bssid': 'bar2', 'channel': 2}

答案 6 :(得分:1)

如前所述,dicts是可变的,因此不能通过set()来操作 - 这是因为无法保证一旦置于集合中,dict将不会改变并变得等于另一个现有元素设置,从而违反了设定的质量。

如果你只是检查dicts是否相等,你可以将它们转换为元组,然后在set()操作中使用元组,然后将结果集中的元组转换回dicts。

>>> d = {1:1, 2:2}
>>> t = tuple(d1.items())
>>> t
((1, 1), (2, 2))
>>> d_ = dict(t)
>>> d_
{1: 1, 2: 2}
>>> d == d_
True

将dicts包装到类中可能会非常麻烦,因为您仍需要解决从dict到不可变数据类型的转换。

如果你的词汇中有列表,你就有更多工作要做。最简单的是,你可以用原始的dicts中的元组替换列表。

假设这不可行,您的转换过程必须是一个函数,而不是分别只调用tuple()和dict()。您需要先将列表转换为元组,然后将元组转换为元组而不是列表转换为元组。例如:

>>> d = {'int1': 1, 'int2': 2, 'list1': ['a', 'b'], 'list2': ['x', 'y']}
>>> d_l = {}
>>> for key, value in d.iteritems():
...   if type(value) == list:
...     d_l[key] = tuple(value)
...   else:
...     d_l[key] = value
>>> d_l
{'int1': 1, 'int2': 2, 'list1': ('a', 'b'), 'list2': ('x', 'y')}
>>> d_ = tuple(d_l.iteritems())
>>> d_
(('int1', 1), ('int2', 2), ('list1', ('a', 'b')), ('list2', ('x', 'y')))

要转换回来,您有两种选择。要么查看您知道的与列表对应的键值(如果您的键已知且不更改),要么查看第二个元素本身为元组的元组(您不在原始序列中存储任何元组)。如果这两个选项都不适用,则必须编写更复杂的转换算法。

答案 7 :(得分:0)

使用列表理解:

>>> l1 = [{1:1, 'a':2},{1:2, 'a':4},{1:5, 'a':'2'}]
>>> l2 = [{1:1, 'a':3},{1:2, 'a':4},{1:5, 'a':'t'}]
>>> l3 = [i for i in l1 if i not in l2]
>>> l3
[{'a': 2, 1: 1}, {'a': '2', 1: 5}]

答案 8 :(得分:0)

我将在这里听到Eric的回答。

首先,手头的问题。为什么dict不可用?简单地说,因为它是一个可变容器。如果更改dict的内容,则哈希值会更改。对于像列表这样的任何其他可变容器也会发生同样的情况。所以,你必须使用不可变的东西。

我认为最简单的解决方案是使用包装类。本质上,一个具有单个属性的类是您最初想要的字典。你可以用你想要比较的任何魔法功能来加强它。

所以,如果我有原始的网络列表

network_list = [
{'found': False, 'flags': ['WPA2-PSK-CCMP', 'WPS', 'ESS'], 'ssid': 'SOHO_BROADCAST', 'bssid': '30:46:9a:9d:11:1a', 'channel': 1},
{'found': False, 'flags': ['WPA-EAP-TKIP', 'WPA2-EAP-CCMP', 'ESS'], 'ssid': 'Cisco 2.4ghz', 'bssid': '40:f4:ec:7f:3c:5a', 'channel': 11},
{'found': False, 'flags': ['WPA-EAP-TKIP', 'WPA2-EAP-CCMP', 'ESS'], 'ssid': 'Cisco 5.0ghz', 'bssid': '40:f4:ec:7f:3c:54', 'channel': 149}
]

我可以轻松应用包装类。

class Wrapper(object):
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

wrapped_networks = [Wrapper(**{'net_dict': network}) for network in network_list]

这样,字典就可以通过

存储和访问
wrapped_networks[0].net_dict # etc...

或其他任何你可能想要命名的东西。此外,由于类的实现方式,您可以使用它来包装您想要的任何内容,即使每个Wrapper有多个内容!

根据在运行时分配给它的唯一id,正如你可能已经知道的那样,make是如此实际得到的哈希是对象。对你的差异进行一点点重构就可以使用这些包装器了,你应该顺利完成(除非你想出一个更好的解决方案= D)