중첩 된 사전 및 목록에서 모든 키 발생 찾기


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는 동일한 문제에 대해 여러 가지 다른 솔루션을 제공하기 때문에 매우 흥미로 웠습니다. 이 모든 기능을 가져 와서 복잡한 사전 객체로 테스트했습니다. 많은 실패 결과를 가져야하고 거의 모든 데이터에 대해 함수를 준비해야하므로 필수라고 생각하는 값으로 목록이나 사전을 반환하는 것을 지원하지 않았기 때문에 테스트에서 두 가지 함수를 제거해야했습니다 .

그래서 나는 timeit모듈을 통해 10,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)는 여기 함수에서 조정 된 내 함수입니다. 실제로 findAlfe 의 함수 와 거의 비슷 하지만, 재귀 중에 문자열이 전달되는 경우 주어진 객체에 iteritems 함수가 있는지 확인하고 있습니다.

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엄청나게 느리고 두 번째로 느리지 get_recursivley만 나머지 dict_extract는 서로 가깝습니다. 기능 funkeyHole유일한 작업은 문자열을 찾고 있다면.

흥미로운 학습 측면 :)


1
당신은 내가 그랬던 것처럼 여러 개의 키를 검색하려면 단지 : (1)로 변경 gen_dict_extract(keys, var)(2) 풋 for key in keys:라인 2로 나머지 (3)에 최초의 수율을 변경 들여 쓰기yield {key: v}
브루노 Bronosky

6
당신은 사과와 오렌지를 비교하고 있습니다. 생성기를 반환하는 함수를 실행하면 완료된 결과를 반환하는 함수를 실행하는 것보다 시간이 덜 걸립니다. next(functionname(k, o)모든 발전기 솔루션 에 대해 timeit을 사용해보십시오 .
kaleissin

6
hasattr(var, 'items')python3에 대한
gobrewers14

1
호출이 실패 할 경우 예외를 포착하기 위해 if hasattr사용하는 버전 의 부분 을 제거하는 것을 고려 했습니까 try( 가능한 구현 은 pastebin.com/ZXvVtV0g 참조 )? 그러면 속성의 두 배 조회 iteritems( hasattr()호출에 대해 한 번 및 호출에 대해 한 번)가 줄어들어 런타임이 감소 할 수 있습니다 (중요하게 보임). 하지만 벤치 마크는 만들지 않았습니다.
Alfe

2
이제 파이썬 3가 점령했다고이 페이지를 방문 누군가를 위해, 그 기억 iteritems되고있다 items.
Mike Williamson

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

감사합니다. 잘 작동합니다. 내 목록에는 문자열과 사전 (내가 언급하지 않은)을 포함 할 수 있기 때문에 약간의 수정이 필요하지만 그렇지 않으면 완벽합니다.
Matt Swain

1
이 맞는 매우 좁은 경우, 당신은 소위 "hexerei 소프트웨어"에서 답 고려하는 것이 자신에게 빚gen_dict_extract
브루노 Bronosky

" 'NoneType'유형의 인수가 반복 가능한없는 형식 오류는"나는 오류가있어
xiaoshir

2
이 솔루션은 목록을 지원하지 않는 것 같습니다
Alex 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에서도 작동합니다. 위의 솔루션 중 어느 것도 목록 내부에 나열된 딕셔너리 내부에 중첩 된 목록이있는 API 응답에 대해 작동하지 않았지만 이것은 아름답게 작동했습니다.
Andy Forceno

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
이것은 잘 작동하지만 문자열을 직접 포함하는 목록을 발견하면 문제가 발생합니다 (예제에 포함하는 것을 잊었습니다). 마지막 두 줄 앞에 isinstance수표를 추가하면 dict문제가 해결됩니다.
Matt Swain

1
찬사를 보내 주셔서 감사합니다.하지만 속도보다는 내 코드의 청결 함 때문에 더 자랑스럽게 생각합니다.
에 AlFe

1
95 %는 그렇습니다. 나머지 (드문) 경우는 시간 제한이있어 더 빠른 버전을 선택해야하는 경우입니다. 그러나 나는 이것을 좋아하지 않는다. 그것은 항상 그 코드를 유지해야 할 후임자에게 많은 작업을하는 것을 의미합니다. 후임자가 혼란 스러울 수 있기 때문에 위험합니다. 그런 다음 내 동기, 타이밍 실험, 결과 등을 설명하는 전체 문서와 같이 많은 의견을 작성해야합니다. 이는 저와 모든 동료가 제대로 수행하기 위해 더 많은 작업을 수행하는 방법입니다. 클리너는 훨씬 간단합니다.
Alfe

2
@Alfe-이 답변에 감사드립니다. Elasticsearch의 특정 사용 사례를 위해 중첩 된 딕셔너리에서 문자열의 모든 발생을 추출해야했고이 코드는 사소한 수정으로 유용했습니다.- stackoverflow.com
questions/40586020/…

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-software의 대답에 대한 훌륭한 모드 : 간결하고 dicts 목록을 허용합니다! 나는 이것을 사용하기 위해 그의 의견에 @ bruno-bronosky의 제안과 함께 사용하고 있습니다 for key in keys. 또한 더 많은 다양성 isinstance(list, tuple)위해 2 차 에 추가했습니다 . ;)
Cometsong

4

이 함수는 중첩 된 사전과 목록을 포함하는 사전을 재귀 적으로 검색합니다. 필드가 발견 될 때마다 값을 포함하는 fields_found라는 목록을 작성합니다. '필드'는 사전과 중첩 된 목록 및 사전에서 찾고있는 키입니다.

def get_recursively (search_dict, field) :
    "" "중첩 된 목록과 사전이있는 사전을 취합니다.
    필드의 키에 대한 모든 사전을 검색합니다.
    제공됩니다.
    "" "
    fields_found = []

    키의 경우 search_dict.iteritems ()의 값 :

        키 == 필드 인 경우 :
            fields_found.append (값)

        elif isinstance (value, dict) :
            결과 = get_recursively (값, 필드)
            결과 결과 :
                fields_found.append (결과)

        elif isinstance (값, 목록) :
            가치 항목 :
                isinstance (item, dict) :
                    more_results = get_recursively (항목, 필드)
                    more_results의 another_result :
                        fields_found.append (another_result)

    field_found 반환

1
다른 루프를 실행하는 대신 fields_found.extend (more_results)를 사용할 수 있습니다. 내 의견으로는 조금 더 깨끗해 보일 것입니다.
sapit

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.