ネストされた辞書とリストでキーのすべての出現を検索します


88

私はこのような辞書を持っています:

{ "id" : "abcde",
  "key1" : "blah",
  "key2" : "blah blah",
  "nestedlist" : [ 
    { "id" : "qwerty",
      "nestednestedlist" : [ 
        { "id" : "xyz",
          "keyA" : "blah blah blah" },
        { "id" : "fghi",
          "keyZ" : "blah blah blah" }],
      "anothernestednestedlist" : [ 
        { "id" : "asdf",
          "keyQ" : "blah blah" },
        { "id" : "yuiop",
          "keyW" : "blah" }] } ] } 

基本的に、任意の深さのネストされたリスト、辞書、および文字列を含む辞書。

これをトラバースしてすべての「id」キーの値を抽出する最良の方法は何ですか?「// id」のようなXPathクエリと同等のものを実現したいと思います。「id」の値は常に文字列です。

したがって、私の例から、必要な出力は基本的に次のとおりです。

["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]

順序は重要ではありません。



None入力として渡すと、ほとんどのソリューションが爆発します。堅牢性は気になりますか?(これは現在、正規の質問として使用されているため)
smci

回答:


74

このQ / Aは、同じ問題に対していくつかの異なる解決策を提供するため、非常に興味深いと思いました。私はこれらすべての関数を取り、複雑な辞書オブジェクトでテストしました。2つの関数をテストから除外する必要がありました。これは、多くの失敗結果が必要であり、リストまたはdictを値として返すことをサポートしていなかったためです。これは、関数がほぼすべてのデータに対して準備される必要があるためです。

そのため、timeitモジュールを介して100.000回の反復で他の関数をポンプし、出力は次の結果になりました。

0.11 usec/pass on gen_dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6.03 usec/pass on find_all_items(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.15 usec/pass on findkeys(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1.79 usec/pass on get_recursively(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.14 usec/pass on find(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.36 usec/pass on dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

すべての関数には、検索するための同じ針( 'logging')と、次のように構成された同じ辞書オブジェクトがありました。

o = { 'temparature': '50', 
      'logging': {
        'handlers': {
          'console': {
            'formatter': 'simple', 
            'class': 'logging.StreamHandler', 
            'stream': 'ext://sys.stdout', 
            'level': 'DEBUG'
          }
        },
        'loggers': {
          'simpleExample': {
            'handlers': ['console'], 
            'propagate': 'no', 
            'level': 'INFO'
          },
         'root': {
           'handlers': ['console'], 
           'level': 'DEBUG'
         }
       }, 
       'version': '1', 
       'formatters': {
         'simple': {
           'datefmt': "'%Y-%m-%d %H:%M:%S'", 
           'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
         }
       }
     }, 
     'treatment': {'second': 5, 'last': 4, 'first': 4},   
     'treatment_plan': [[4, 5, 4], [4, 5, 4], [5, 5, 5]]
}

すべての機能で同じ結果が得られましたが、時差は劇的です。この関数gen_dict_extract(k,o)は、ここの関数を応用した私の関数です。実際には、find、Alfeの関数とほとんど同じですが、主な違いは、再帰中に文字列が渡された場合に、指定されたオブジェクトにiteitems関数があるかどうかを確認することです。

def gen_dict_extract(key, var):
    if hasattr(var,'iteritems'):
        for k, v in var.iteritems():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

したがって、このバリアントは、ここでの関数の中で最も高速で安全です。そしてfind_all_items、信じられないほど遅く、2番目に遅いものから遠く離れていますがget_recursivley、残りは、を除いてdict_extract、互いに近くにあります。機能funkeyHole唯一の仕事は、あなたは、文字列を探している場合。

ここで興味深い学習の側面:)


1
私が行ったように複数のキーを検索する場合は、(1)に変更しますgen_dict_extract(keys, var)(2)for key in keys:行2として配置し、残りをインデントします(3)最初の歩留まりをyield {key: v}
Bruno Bronosky 2017

6
あなたはリンゴとオレンジを比較しています。ジェネレーターを返す関数を実行すると、完成した結果を返す関数を実行するよりも時間がかかりません。next(functionname(k, o)すべてのジェネレータソリューションでtimeitを試してください。
kaleissin 2017

6
hasattr(var, 'items')python3の場合
gobrewers14

1
呼び出しが失敗した場合に例外をキャッチするためにif hasattr使用するバージョンのパーツを削除することを検討しましたかtry(可能な実装についてはpastebin.com/ZXvVtV0gを参照してください)。これにより、属性の2倍のルックアップiteritemshasattr()呼び出しに対して1回と1回)が減り、実行時間が短縮される可能性があります(これはあなたにとって重要と思われます)。ただし、ベンチマークは作成しませんでした。
アルフェ2017年

2
Python 3が引き継いだので、このページにアクセスする人は、それiteritemsがになっitemsたことを覚えておいてください。
マイクウィリアムソン

46
d = { "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [ 
    { "id" : "qwerty",
        "nestednestedlist" : [ 
        { "id" : "xyz", "keyA" : "blah blah blah" },
        { "id" : "fghi", "keyZ" : "blah blah blah" }],
        "anothernestednestedlist" : [ 
        { "id" : "asdf", "keyQ" : "blah blah" },
        { "id" : "yuiop", "keyW" : "blah" }] } ] } 


def fun(d):
    if 'id' in d:
        yield d['id']
    for k in d:
        if isinstance(d[k], list):
            for i in d[k]:
                for j in fun(i):
                    yield j

>>> list(fun(d))
['abcde', 'qwerty', 'xyz', 'fghi', 'asdf', 'yuiop']

私は変化するであろう唯一のものはあるfor k in dfor k,value in d.items()のその後の使用でvalueはなく、d[k]
ovgolovin 2012年

おかげで、これはうまくいきます。私のリストには文字列とdict(私は言及しませんでした)を含めることができるため、ごくわずかな変更が必要でしたが、それ以外は完璧です。
マットスウェイン2012年

1
これは非常に狭いケースに当てはまります。「hexereiソフトウェア」からの回答を検討する必要がありますgen_dict_extract
Bruno Bronosky 2017

私は、エラー「TypeError例外を:型『NoneType』の引数は反復可能ではありません」だ
xiaoshir

2
このソリューションは、リストをサポートしていないようだ
アレックスR

24
d = { "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [
    { "id" : "qwerty",
        "nestednestedlist" : [
        { "id" : "xyz", "keyA" : "blah blah blah" },
        { "id" : "fghi", "keyZ" : "blah blah blah" }],
        "anothernestednestedlist" : [
        { "id" : "asdf", "keyQ" : "blah blah" },
        { "id" : "yuiop", "keyW" : "blah" }] } ] }


def findkeys(node, kv):
    if isinstance(node, list):
        for i in node:
            for x in findkeys(i, kv):
               yield x
    elif isinstance(node, dict):
        if kv in node:
            yield node[kv]
        for j in node.values():
            for x in findkeys(j, kv):
                yield x

print(list(findkeys(d, 'id')))

1
この例は、私がテストしたすべての複雑な辞書で機能しました。よくやった。

これが受け入れられた答えをする必要があり、それはリストのリスト内にネストされている辞書内にある鍵を見つけることができるなど
Anthonの

これは、最後のprintステートメントが変更されている限り、Python3でも機能します。上記のソリューションはいずれも、リスト内にリストされたdict内にリストがネストされたAPI応答では機能しませんでしたが、これは見事に機能しました。
AndyForceno19年

21
def find(key, value):
  for k, v in value.iteritems():
    if k == key:
      yield v
    elif isinstance(v, dict):
      for result in find(key, v):
        yield result
    elif isinstance(v, list):
      for d in v:
        for result in find(key, d):
          yield result

編集:@Anthonは、これが直接ネストされたリストでは機能しないことに気づきました。入力にこれがある場合は、これを使用できます。

def find(key, value):
  for k, v in (value.iteritems() if isinstance(value, dict) else
               enumerate(value) if isinstance(value, list) else []):
    if k == key:
      yield v
    elif isinstance(v, (dict, list)):
      for result in find(key, v):
        yield result

ただし、元のバージョンの方がわかりやすいと思いますので、そのままにしておきます。


1
これもうまく機能しますが、文字列を直接含むリスト(例に含めるのを忘れた)が見つかった場合も同様に問題が発生します。最後の2行の前isinstanceにaのチェックを追加すると、dictこれが解決すると思います。
マットスウェイン2012年

1
称賛に感謝しますが、コードの速度よりもコードのクリーンさで称賛を得ることができたことを誇りに思います。
アルフェ2015

1
95%の確率でそうです 残りの(まれな)機会は、時間の制限により、よりクリーンなバージョンよりも高速なバージョンを選択せざるを得ない場合です。しかし、私はこれが好きではありません。それは常に、そのコードを維持しなければならない後継者に大量の作業をかけることを意味します。私の後継者が混乱するかもしれないので、それはリスクです。その場合、私は多くのコメントを書く必要があります。おそらく、私の動機、タイミング実験、それらの結果などを説明するドキュメント全体です。それは、私とすべての同僚がそれを適切に行うためのより多くの作業です。クリーナーはずっと簡単です。
Alfe 2016

2
@ Alfe-この回答に感謝します。私はElasticsearchの具体的なユースケースのために、ネストされた辞書内の文字列のすべての出現箇所を抽出する必要性を持っていたし、このコードは、マイナーな修正で有用であった- stackoverflow.com/questions/40586020/...
Saurabh Hirani

1
これ、リストに直接含まれているリストでは完全に壊れます。
Anthonの

5

見つかった結果へのネストされたパスを含む別のバリエーション(注:このバージョンはリストを考慮していません):

def find_all_items(obj, key, keys=None):
    """
    Example of use:
    d = {'a': 1, 'b': 2, 'c': {'a': 3, 'd': 4, 'e': {'a': 9, 'b': 3}, 'j': {'c': 4}}}
    for k, v in find_all_items(d, 'a'):
        print "* {} = {} *".format('->'.join(k), v)    
    """
    ret = []
    if not keys:
        keys = []
    if key in obj:
        out_keys = keys + [key]
        ret.append((out_keys, obj[key]))
    for k, v in obj.items():
        if isinstance(v, dict):
            found_items = find_all_items(v, key, keys=(keys+[k]))
            ret += found_items
    return ret

5

yield fromトップレベルのリストを使用して受け入れる@ hexerei-softwareの優れた答えを繰り返したかっただけです。

def gen_dict_extract(var, key):
    if isinstance(var, dict):
        for k, v in var.items():
            if k == key:
                yield v
            if isinstance(v, (dict, list)):
                yield from gen_dict_extract(v, key)
    elif isinstance(var, list):
        for d in var:
            yield from gen_dict_extract(d, key)

@ hexereiへの優れたmod-ソフトウェアの答え:簡潔で、辞書のリストを許可します!私はこれを@ bruno-bronoskyのコメントでの提案と一緒に使用していますfor key in keys。また、私は第二に追加isinstance(list, tuple)さえため、より多様。;)
Cometsong19年

4

この関数は、ネストされた辞書とリストを含む辞書を再帰的に検索します。これは、fields_foundというリストを作成します。このリストには、フィールドが見つかるたびの値が含まれています。「フィールド」は、辞書とそのネストされたリストおよび辞書で私が探しているキーです。

def get_recursively(search_dict、field):
    "" "ネストされたリストとdictを使用してdictを取得し、
    すべてのdictでフィールドのキーを検索します
    提供されます。
    "" "
    fields_found = []

    キーの場合、search_dict.iteritems()の値:

        キー==フィールドの場合:
            fields_found.append(value)

        elif isinstance(value、dict):
            結果= get_recursively(value、field)
            結果の結果:
                fields_found.append(result)

        elif isinstance(value、list):
            価値のあるアイテムの場合:
                isinstance(item、dict)の場合:
                    more_results = get_recursively(item、field)
                    more_resultsのanother_resultの場合:
                        fields_found.append(another_result)

    fields_foundを返す

1
別のループを実行する代わりに、fields_found.extend(more_results)を使用できます。私の意見では少しきれいに見えるでしょう。
sapit 2018年

0

これが私の刺し傷です:

def keyHole(k2b,o):
  # print "Checking for %s in "%k2b,o
  if isinstance(o, dict):
    for k, v in o.iteritems():
      if k == k2b and not hasattr(v, '__iter__'): yield v
      else:
        for r in  keyHole(k2b,v): yield r
  elif hasattr(o, '__iter__'):
    for r in [ keyHole(k2b,i) for i in o ]:
      for r2 in r: yield r2
  return

例:

>>> findMe = {'Me':{'a':2,'Me':'bop'},'z':{'Me':4}}
>>> keyHole('Me',findMe)
<generator object keyHole at 0x105eccb90>
>>> [ x for x in keyHole('Me',findMe) ]
['bop', 4]

0

キーのリスト/セットを反復処理する場合は、@ hexereiソフトウェアの回答と@ bruno-bronoskyのコメントをフォローアップします。

def gen_dict_extract(var, keys):
   for key in keys:
      if hasattr(var, 'items'):
         for k, v in var.items():
            if k == key:
               yield v
            if isinstance(v, dict):
               for result in gen_dict_extract([key], v):
                  yield result
            elif isinstance(v, list):
               for d in v:
                  for result in gen_dict_extract([key], d):
                     yield result    

文字列キーの代わりに、単一の要素([key]}を含むリストを渡していることに注意してください。


0

pip install nested-lookup あなたが探していることを正確に行います:

document = [ { 'taco' : 42 } , { 'salsa' : [ { 'burrito' : { 'taco' : 69 } } ] } ]

>>> print(nested_lookup('taco', document))
[42, 69]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.