İç içe geçmiş sözlüklerde ve listelerde bir anahtarın tüm oluşumlarını bulun


88

Bunun gibi bir sözlüğüm var:

{ "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" }] } ] } 

Temel olarak, keyfi derinlikte iç içe geçmiş listeler, sözlükler ve dizeler içeren bir sözlük.

Her "id" anahtarının değerlerini çıkarmak için bunu geçmenin en iyi yolu nedir? "// id" gibi bir XPath sorgusunun eşdeğerini elde etmek istiyorum. "İd" değeri her zaman bir dizedir.

Örneğimden ihtiyacım olan çıktı temelde:

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

Düzen önemli değil.



NoneGirdi olarak geçersek çözümlerinizin çoğu patlar. Sağlamlığı önemsiyor musunuz? (bu artık kanonik soru olarak kullanıldığından)
smci

Yanıtlar:


74

Bu S / C'yi çok ilginç buldum, çünkü aynı problem için birkaç farklı çözüm sunuyor. Tüm bu işlevleri aldım ve karmaşık bir sözlük nesnesiyle test ettim. Testten iki işlevi çıkarmak zorunda kaldım, çünkü çok sayıda başarısız sonuç vardı ve listelerin veya diktelerin değer olarak döndürülmesini desteklemiyorlardı ki bu benim gerekli bulduğum, çünkü hemen hemen her verinin gelmesi için bir işlevin hazırlanması gerekiyordu .

Bu yüzden diğer fonksiyonları 100.000 yinelemeyle timeitmodül üzerinden pompaladım ve çıktı aşağıdaki sonuca geldi:

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

Tüm işlevler aramak için aynı iğneye sahipti ('günlüğe kaydetme') ve aynı sözlük nesnesi şu şekilde oluşturuldu:

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

Tüm işlevler aynı sonucu verdi, ancak zaman farklılıkları çarpıcı! İşlev gen_dict_extract(k,o), buradaki işlevlerden uyarlanmış findişlevimdir , aslında Alfe'nin işlevine çok benzer , temel farkla, özyineleme sırasında dizelerin iletilmesi durumunda verilen nesnenin yineleme işlevi olup olmadığını kontrol ediyorum:

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

Yani bu değişken, buradaki işlevlerin en hızlı ve en güvenli olanıdır. Ve find_all_itemsinanılmaz derecede yavaş ve ikinciden çok uzak get_recursivley, geri kalanlar dict_extractise birbirine yakın. İşlevler funve keyHoleyalnızca dizeleri arıyorsanız çalışır.

Burada ilginç öğrenme yönü :)


1
Benim yaptığım gibi birden fazla anahtar aramak istiyorsanız, sadece: (1) gen_dict_extract(keys, var)2. for key in keys:satır olarak değiştirin ve geri kalanını girintileyin (3) ilk verimiyield {key: v}
Bruno Bronosky olarak

6
Elmaları portakallarla karşılaştırıyorsunuz. Oluşturucu döndüren bir işlevi çalıştırmak, bitmiş bir sonuç döndüren bir işlevi çalıştırmaktan daha az zaman alır. next(functionname(k, o)Tüm jeneratör çözümleri için timeit on'u deneyin .
kaleissin

6
hasattr(var, 'items')python3 için
gobrewers14

1
Çağrının başarısız olması durumunda istisnayı yakalamak if hasattriçin bir sürümün parçasını çıkarmayı düşündünüz mü try( olası bir uygulama için pastebin.com/ZXvVtV0g adresine bakın )? Bu, özniteliğin iki katına çıkarılmasını azaltır iteritems(bir kez arama için hasattr()ve bir kez) ve böylece muhtemelen çalışma süresini azaltır (bu sizin için önemli görünüyor). Yine de herhangi bir kıyaslama yapmadı.
Alfe

2
Python 3 devralındığı için bu sayfayı şimdi ziyaret eden herkes için, iteritemsbunun haline geldiğini unutmayın 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']

Ben değiştirecek tek şey for k in diçin for k,value in d.items()sonraki kullanımı ile valueyerine d[k].
ovgolovin

Teşekkürler, bu harika çalışıyor. Listelerim dizelerin yanı sıra diktler (bahsetmediğim) içerebileceğinden çok küçük değişiklikler gerekli, ancak aksi halde mükemmel.
Matt Swain

1
Bu uyan çok dar durumda aradığını "Hexerei yazılımı" cevabını düşünmeye kendine borçlusungen_dict_extract
Bruno Bronosky

"TypeError: 'NoneType' türündeki bağımsız değişken yinelenemez"
hatasını

2
Bu çözüm listeleri desteklemiyor gibi görünüyor
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
Bu örnek, test ettiğim her karmaşık sözlükle çalıştı. Aferin.

Bu kabul edilen cevap olmalı, listeler vb. Listelerde yer alan sözlüklerdeki anahtarları bulabilir.
Anthon

Bu, sondaki print ifadesi değiştirildiği sürece Python3'te de çalışır. Yukarıdaki çözümlerden hiçbiri, listelerin içinde vb. Listelenen talimatların içinde yer alan bir API yanıtı için işe yaramadı, ancak bu çok güzel çalıştı.
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

DÜZENLEME: @Anthon, bunun doğrudan iç içe geçmiş listeler için çalışmayacağını fark etti. Girişinizde bu varsa, bunu kullanabilirsiniz:

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

Ama bence orijinal versiyonun anlaşılması daha kolay, bu yüzden bırakacağım.


1
Bu da harika çalışıyor, ancak aynı şekilde doğrudan bir dize içeren bir listeyle karşılaşırsa (örneğime eklemeyi unuttuğum) sorunlarla karşılaşır. Sanırım son iki satırdan önce bir isinstancekontrol eklemek bunu dictçözüyor.
Matt Swain

1
Övgüler için teşekkürler, ancak kodumun temizliği için hızından çok onları almaktan gurur duyarım.
Alfe

1
Zamanın% 95'i, evet. Kalan (nadir) durumlar, biraz zaman sınırlamasının beni daha temiz olana göre daha hızlı bir sürümü seçmeye zorlayabileceği durumlardır. Ama bundan hoşlanmadım. Her zaman, bu kodu korumak zorunda kalacak olan halefime bir yük koymak anlamına gelir. Bu bir risk çünkü halefimin kafası karışabilir. O zaman pek çok yorum yazmam gerekecek, belki motivasyonlarımı, zamanlama deneylerimi, sonuçlarını vb. Açıklayan bütün bir belge. Bu, benim ve tüm meslektaşlarımın bunu düzgün bir şekilde yapması için çok daha fazla iş. Temizleyici çok daha basittir.
Alfe

2
@Alfe - bu cevap için teşekkürler. Elasticsearch'ün belirli bir kullanım durumu için iç içe geçmiş bir diktede bir dizenin tüm oluşumlarını çıkarmaya ihtiyacım vardı ve bu kod küçük bir değişiklikle faydalı oldu - stackoverflow.com/questions/40586020/…
Saurabh Hirani

1
Bu , doğrudan listelerde bulunan listelerde tamamen bozulur .
Anthon

5

Bulunan sonuçların iç içe geçmiş yolunu içeren başka bir varyasyon ( not: bu sürüm listeleri dikkate almaz ):

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

Sadece @ hexerei-software'in en yield fromüst düzey listeleri kullanarak ve kabul ederek mükemmel yanıtını yinelemek istedim .

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'in cevabına mükemmel bir mod: kısa ve öz ve dicts listesine izin veriyor! Bunu yorumlarında kullanmak için @ bruno-bronosky'nin önerileriyle birlikte kullanıyorum for key in keys. Ayrıca ben 2. eklendi isinstanceiçin (list, tuple)için bile fazla çeşitlilik. ;)
Cometsong

4

Bu işlev, iç içe geçmiş sözlükler ve listeler içeren bir sözlüğü yinelemeli olarak arar. Alan her bulunduğunda geçerli olan değeri içeren, Fields_found adlı bir liste oluşturur. 'Alan', sözlükte ve onun iç içe geçmiş listelerinde ve sözlüklerinde aradığım anahtardır.

def get_recursively (search_dict, alan):
    "" "İç içe geçmiş listeler ve diktelerle bir dikte alır,
    ve alanın bir anahtarı için tüm sözcükleri arar
    sağlanan.
    "" "
    Fields_found = []

    anahtar için search_dict.iteritems () içindeki değer:

        eğer anahtar == alanı:
            Fields_found.append (değer)

        elif isinstance (değer, dikte):
            sonuçlar = get_recursively (değer, alan)
            sonuçlarla sonuçlanmak için:
                Fields_found.append (sonuç)

        elif isinstance (değer, liste):
            değerdeki öğe için:
                durum ise (öğe, dikte):
                    more_results = get_recursively (öğe, alan)
                    more_results içinde another_result için:
                        Fields_found.append (another_result)

    alanlar_found döndür

1
Başka bir döngü çalıştırmak yerine Fields_found.extend (more_results) işlevini kullanabilirsiniz. Bence biraz daha temiz görünür.
sapit

0

İşte ona bıçağım:

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

Ör .:

>>> 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 yazılımının yanıtını ve @ bruno-bronosky'nin yorumunu takip ederek, bir liste / anahtar dizisi üzerinde yinelemek istiyorsanız:

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    

Dize anahtarı yerine tek bir öğe ([key]} içeren bir liste geçirdiğimi unutmayın.


0

pip install nested-lookup tam olarak aradığınız şeyi yapar:

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

>>> print(nested_lookup('taco', document))
[42, 69]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.