4

複数のリストを比較し、違いを取り、それを繰り返すことに興味があります。

どちらも次のキーを含む dict のリストです: 'ssid' - str、'bssid' - str、'channel' - int、'flags' - リスト、'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

このような一般的なアプローチにはおそらく多くの落とし穴がありますが、辞書がほとんどプリミティブであり、巨大ではない場合は、次のようにすることができます。

データが次のようになっていると仮定します。

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 に答える
4

それらをdictのリストにする代わりに、それらを実装するオブジェクトのリストにし、__eq__提供__hash__するコードが機能するはずです

于 2012-08-23T22:21:43.430 に答える
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 に答える
2

dict は変更可能なアイテムです。これは、そのライフサイクルを通じて一定のハッシュ値を持たず、セットに入れることができないことを意味します。

すべての辞書を同じ関数で文字列に変換すると、ハッシュ可能になり、セットで使用できます...

于 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

いいえ、一般的に効率的に行うのはかなり難しいです。ただし、詳細を説明していない特定のデータ構造についてのみ、一般的なケースを解決する必要はありません。

たとえば、辞書キーがすべてである場合、intまたはstrキーが複素数である場合よりもかなり簡単な場合など.

編集:データ構造を教えてくれたので、簡単な方法は dicts をnametuplesに変換することだと言えます。

注: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')])
于 2012-08-23T22:18:54.310 に答える
1

前述のように、辞書は変更可能であり、set() で操作することはできません。これは、一度セット内に配置された辞書が変更されず、セットの別の既存の要素と等しくならないという保証がないためです。したがって、セット品質に違反しています。

dict が等しいかどうかのみをチェックする場合は、それらをタプルに変換してから、set() 操作でタプルを使用し、結果のセット内のタプルを dict に戻すことができます。

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

辞書をクラスにラップすることは、辞書から不変のデータ型への変換を解決する必要があるため、かなり面倒になる可能性があります。

辞書内にリストがあるので、より多くの作業があります。最も簡単なのは、元の辞書のリストをタプルに置き換えることができる場合です。

それが不可能であると仮定すると、それぞれ 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')))

元に戻すには、2 つのオプションがあります。リストに対応することがわかっているキー値を確認するか (キーが既知であり、変更されていない場合)、2 番目の要素がタプル自体であるタプルを確認します (元の辞書にタプルを格納していない場合)。どちらのオプションも当てはまらない場合は、より複雑な変換アルゴリズムを作成する必要があります。

于 2012-08-30T05:13:39.220 に答える
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 に答える
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 に答える