नेस्टेड शब्दकोशों और सूचियों में एक कुंजी के सभी घटनाओं का पता लगाएं


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

मूल रूप से नेस्टेड सूचियों, शब्दकोशों और तार के साथ एक शब्दकोश, मनमाना गहराई का।

हर "आईडी" कुंजी के मानों को निकालने के लिए यह पता लगाने का सबसे अच्छा तरीका क्या है? मैं "// आईडी" जैसी XPath क्वेरी के बराबर प्राप्त करना चाहता हूं। "आईडी" का मान हमेशा एक स्ट्रिंग होता है।

इसलिए मेरे उदाहरण से, मुझे जो आउटपुट चाहिए वह मूल रूप से है:

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

आदेश महत्वपूर्ण नहीं है।



यदि हम Noneइनपुट के रूप में पास होते हैं तो आपके अधिकांश समाधान उड़ जाते हैं । क्या आप मजबूती के बारे में परवाह करते हैं? (इस के बाद से अब विहित प्रश्न के रूप में इस्तेमाल किया जा रहा है)
एसएमसीआई

जवाबों:


74

मुझे यह क्यू / ए बहुत दिलचस्प लगा, क्योंकि यह एक ही समस्या के लिए कई अलग-अलग समाधान प्रदान करता है। मैंने इन सभी कार्यों को लिया और एक जटिल शब्दकोश वस्तु के साथ उनका परीक्षण किया। मुझे परीक्षण से दो कार्य लेने थे, क्योंकि उनके कई असफल परिणाम थे और उन्होंने मान के रूप में सूची या डाइक को वापस करने का समर्थन नहीं किया था, जो मुझे आवश्यक लगता है, क्योंकि किसी भी डेटा के आने के लिए एक फ़ंक्शन तैयार किया जाना चाहिए ।

इसलिए मैंने 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)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

सभी फ़ंक्शंस में ('लॉगिंग') और समान डिक्शनरी ऑब्जेक्ट के लिए खोज करने के लिए एक ही सुई थी, जिसे इस तरह से बनाया गया है:

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एल्फ से फ़ंक्शन की तरह बहुत अधिक है , मुख्य अंतर के साथ, कि मैं जाँच कर रहा हूं कि क्या दी गई वस्तु में पुनरावृत्तियाँ फ़ंक्शन हैं, मामले में पुनरावृत्ति के दौरान पास किए जाते हैं:

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एक दूसरे के करीब है। कार्य funऔर keyHoleकेवल तभी काम करते हैं जब आप तार की तलाश कर रहे हों।

दिलचस्प सीखने का पहलू :)


1
यदि आप कई कुंजियों की खोज करना चाहते हैं, जैसा कि मैंने किया, तो बस: (1) में परिवर्तन करें gen_dict_extract(keys, var)(2) for key in keys:पंक्ति 2 के रूप में डालें और बाकी को (3) पहली उपज में बदल देंyield {key: v}
ब्रूनो ब्रोंस्की

6
आप सेब की तुलना संतरे से कर रहे हैं। एक फ़ंक्शन चलाने वाला जो एक जनरेटर लौटाता है, एक फ़ंक्शन चलाने से कम समय लेता है जो एक समाप्त परिणाम देता है। next(functionname(k, o)सभी जनरेटर समाधान के लिए समय पर प्रयास करें ।
कलीसिन

6
hasattr(var, 'items')python3 के लिए
gobrewers14

1
क्या आप कॉल विफल होने की स्थिति में अपवाद को पकड़ने के लिए if hasattrसंस्करण का उपयोग tryकरने के लिए भाग को हटाने पर विचार करते हैं ( संभव कार्यान्वयन के लिए pastebin.com/ZXvVtV0g देखें )? यह विशेषता के दोहरे लुक को कम कर देगा iteritems(एक hasattr()बार कॉल के लिए और एक बार) और इस प्रकार संभवतः रनटाइम को कम करता है (जो आपके लिए महत्वपूर्ण लगता है)। हालांकि कोई मानक नहीं बनाया।
अल्फ

2
इस पेज पर जाने वाले किसी भी व्यक्ति के लिए जिसे पायथन 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 dकरने के लिए for k,value in d.items()के बाद के उपयोग के साथ valueके बजाय d[k]
ओवलगोलोविन

धन्यवाद, यह बहुत अच्छा काम करता है। बहुत मामूली संशोधन की आवश्यकता है क्योंकि मेरी सूचियों में तार के साथ-साथ डाइट भी हो सकते हैं (जिनका मैंने उल्लेख नहीं किया था), लेकिन अन्यथा एकदम सही।
मैट स्वैन

1
यह एक बहुत ही संकीर्ण मामले में फिट बैठता है, आप इसे "हेक्सेरी सॉफ्टवेयर" से उत्तर पर विचार करने के लिए खुद पर एहसान करते हैंgen_dict_extract
ब्रूनो ब्रोंस्की

मुझे त्रुटि मिली "TypeError: बहस का प्रकार 'कोई नहीं टाइप' इट इटरेबल नहीं है
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
इस उदाहरण ने मेरे द्वारा परीक्षण किए गए प्रत्येक जटिल शब्दकोश के साथ काम किया। बहुत बढ़िया।

यह स्वीकृत उत्तर होना चाहिए, यह उन कुंजियों को खोज सकता है जो शब्दकोशों के भीतर हैं जो सूची की सूची के भीतर नेस्टेड हैं
एंथन

यह पायथन 3 में भी काम करता है, जब तक कि अंत में प्रिंट स्टेटमेंट को संशोधित नहीं किया जाता है। इसके ऊपर दिए गए समाधानों में से कोई भी एपीआई की प्रतिक्रिया के लिए काम नहीं करता है, सूची के अंदर सूचीबद्ध सूची, आदि के साथ नेस्टेड नेस्ट के साथ, लेकिन इस एक ने खूबसूरती से काम किया।
एंडी फोर्केनो

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

EDIT: @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यह हल हो जाता है।
मैट स्वैन

1
वाहवाही के लिए धन्यवाद, लेकिन मैं अपनी गति की तुलना में अपने कोड की स्वच्छता के लिए उन्हें प्राप्त करने के लिए बहुत उत्सुक हूं।
एल्फ

1
95% समय, हाँ। शेष (दुर्लभ) अवसर वे हैं जिनमें कुछ समय सीमा मुझे एक क्लीनर पर एक तेज संस्करण चुनने के लिए मजबूर कर सकती है। लेकिन मुझे यह पसंद नहीं है। यह हमेशा मेरे उत्तराधिकारी पर काम का बोझ डालने का मतलब है, जिसे उस कोड को बनाए रखना होगा। यह एक जोखिम है क्योंकि मेरे उत्तराधिकारी भ्रमित हो सकते हैं। मुझे तब बहुत सारी टिप्पणियाँ लिखनी होंगी, हो सकता है कि एक पूरे दस्तावेज़ में मेरे प्रेरणाओं, समय के प्रयोगों, उनके परिणामों आदि के बारे में बताया गया हो। यह मेरे लिए और सभी सहयोगियों के लिए और अधिक काम है। क्लीनर सरल तरीका है।
अल्फ

2
@ शेल्फ - इस उत्तर के लिए धन्यवाद। मुझे एलास्टिसर्च के एक विशिष्ट उपयोग के मामले में एक नेस्टेड तानाशाह में एक स्ट्रिंग के सभी घटनाओं को निकालने की आवश्यकता थी और यह कोड एक मामूली संशोधन के साथ उपयोगी था - stackoverflow.com/questions/40586020/…
सौरभ हिरानी

1
यह पूरी तरह से सीधे सूचियों में निहित सूचियों पर टूट जाता है
एंथन

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शीर्ष-स्तरीय सूचियों का उपयोग करके और स्वीकार करके @ हेक्सेरी-सॉफ़्टवेयर के उत्कृष्ट उत्तर पर चलना चाहता था ।

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)

@Xerei-software के उत्तर के लिए उत्कृष्ट मोड: रसीला और सूची-के-डिकेट्स की अनुमति देता है! मैं उपयोग करने के लिए उनकी टिप्पणियों में @ bruno-bronosky के सुझावों के साथ इसका उपयोग कर रहा हूं for key in keys। इसके अलावा, मैंने और भी विविधता के लिए 2 isinstanceको जोड़ा । ;)(list, tuple)
अगस्त को धूमकेतु

4

यह फ़ंक्शन पुनरावर्ती शब्दकोश और सूचियों वाले शब्दकोश की खोज करता है। यह फ़ील्ड_फाउंड नामक एक सूची बनाता है, जिसमें हर बार फ़ील्ड के मिलने का मान होता है। 'फ़ील्ड' वह कुंजी है जिसे मैं शब्दकोश और इसके नेस्टेड सूचियों और शब्दकोशों में खोज रहा हूँ।

def get_recursively (search_dict, फ़ील्ड):
    "" नेस्टेड सूचियों और dicts के साथ एक ताना लेता है,
    और क्षेत्र की एक कुंजी के लिए सभी dicts को खोजता है
    प्रदान की है।
    "" "
    फ़ील्ड_फाउंड = []

    कुंजी के लिए, search_dict.iteritems () में मान:

        यदि कुंजी == फ़ील्ड:
            fields_found.append (मान)

        एलिफ़स्टेंस (मूल्य, तानाशाह):
            परिणाम = get_recursively (मान, क्षेत्र)
            परिणामों में परिणाम के लिए:
                fields_found.append (परिणाम)

        एलिफेंस (मूल्य, सूची):
            मूल्य में आइटम के लिए:
                अगर प्रतिपूर्ति (मद, तानाशाह):
                    more_results = get_recursively (आइटम, फ़ील्ड)
                    more_results में अन्य के लिए:
                        fields_found.append (another_result)

    लौटते हुए फ़ील्ड_फाउंड

1
आप किसी अन्य लूप को चलाने के बजाय फ़ील्ड_फाउंड.केंडेंड (more_results) का उपयोग कर सकते हैं। मेरी राय में थोड़ा साफ होगा।
सपिट

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 सॉफ्टवेयर के उत्तर और @ ब्रूनो-ब्रोंस्की की टिप्पणी के बाद, यदि आप सूची की कुंजी सेट करना चाहते हैं:

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    

ध्यान दें कि मैं स्ट्रिंग कुंजी के बजाय एक एकल तत्व ([कुंजी]}) के साथ एक सूची पारित कर रहा हूं।


0

pip install nested-lookup ठीक वही है जो आप देख रहे हैं:

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

>>> print(nested_lookup('taco', document))
[42, 69]
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.