4

我有兴趣比较多个列表,获取差异并迭代它。

两者都是包含以下键的字典列表:'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}]

缺失的网络最初是空的。

有没有一种简单的方法可以做到这一点?

4

9 回答 9

4

与其让它们成为 dicts 列表,不如让它们成为实现的对象列表,__eq__并且__hash__您提供的代码应该可以工作

于 2012-08-23T22:21:43.430 回答
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]

更新

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

于 2012-08-23T22:24:17.517 回答
2

不,通常很难有效地做到这一点。不过,您不必解决一般情况,只需针对您尚未向我们详细说明的特定数据结构。

例如,如果您的 dict 键是 allint或者str它比键是复数等要容易得多。

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

注意:您不能只使用tuple(dict.items()) 将字典转换为元组,因为键的顺序可能从一个字典到下一个字典不同

>>> 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')])
于 2012-08-23T22:18:54.310 回答
2

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

如果您将所有 dicts 转换为具有相同功能的字符串,它们将变为可散列的,您可以在集合中使用它们...

于 2012-08-23T22:23:37.847 回答
2

如果您尝试以下简单的方法会怎样:

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

(顺便说一句:我在这里将您的变量命名为lst;鉴于 Python 支持list()函数,将一些结果绑定到名称“list”可能是个坏主意。它不是关键字,因此不会引发异常,但是当您编写一些稍后尝试调用该list()函数的代码时,您可能会绊倒它)。

于 2012-08-23T22:55:34.720 回答
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}
于 2012-08-24T03:22:21.277 回答
1

使用列表推导:

>>> 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}]
于 2012-08-23T22:40:45.753 回答
1

如前所述,dict 是可变的,因此不能通过 set() 操作——这是因为无法保证一旦放入 set 中,dict 就不会改变并等于该 set 的另一个现有元素,因此违反规定的质量。

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

>>> 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

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

由于您的字典中有列表,因此您有更多的工作。最简单的是,如果你可以用原始字典中的元组替换列表。

假设这不可行,您的转换过程必须是一个函数,而不是分别调用 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')))

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

于 2012-08-30T05:13:39.220 回答
0

我将在这里接受埃里克的回答。

首先,眼前的问题。为什么字典不可散列?简单地说,因为它是一个可变容器。如果您更改 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,实际上被散列的是对象。对您的差异函数进行一些重构以使用这些包装器,并且您应该一切顺利(除非您提出更好的解决方案 =D )

于 2012-08-30T01:25:06.460 回答