नेस्टेड शब्दकोशों को लागू करने का सबसे अच्छा तरीका क्या है?


201

मेरे पास एक डेटा संरचना है जो अनिवार्य रूप से एक नेस्टेड शब्दकोश में मात्रा है। मान लीजिए कि यह ऐसा दिखता है:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

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

मैं भी कुंजी के रूप में tuples का उपयोग कर सकता है, जैसे:

{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

यह मूल्यों को बहुत सरल और स्वाभाविक बनाता है, लेकिन एकत्रीकरण जैसी चीजों को करना और शब्दकोश के सबसेट (जैसे अगर मैं सिर्फ राज्य-दर-राज्य जाना चाहता हूं) को देखना अधिक सरल रूप से दर्दनाक है।

मूल रूप से, कभी-कभी मैं एक नेस्टेड शब्दकोश को एक फ्लैट शब्दकोश के रूप में सोचना चाहता हूं, और कभी-कभी मैं इसे वास्तव में एक जटिल पदानुक्रम के रूप में सोचना चाहता हूं। मैं यह सब एक वर्ग में लपेट सकता हूं, लेकिन ऐसा लगता है कि किसी ने पहले ही ऐसा कर लिया होगा। वैकल्पिक रूप से, ऐसा लगता है कि ऐसा करने के लिए कुछ बहुत ही सुंदर वाक्यविन्यास निर्माण हो सकते हैं।

मैं यह कैसे बेहतर कर सकता था?

परिशिष्ट: मैं जानता हूँ, setdefault()लेकिन यह वास्तव में स्वच्छ वाक्यविन्यास के लिए नहीं है। इसके अलावा, आपके द्वारा बनाए गए प्रत्येक उप-शब्दकोश को अभी भी setdefault()मैन्युअल रूप से सेट करने की आवश्यकता है ।

जवाबों:


178

पायथन में नेस्टेड शब्दकोशों को लागू करने का सबसे अच्छा तरीका क्या है?

यह एक बुरा विचार है, यह मत करो। इसके बजाय, एक नियमित शब्दकोश का उपयोग करें और dict.setdefaultजहां एप्रोपोस का उपयोग करें , इसलिए जब सामान्य उपयोग के तहत चाबियाँ गायब हों, तो आप अपेक्षित हो KeyError। यदि आप इस व्यवहार को प्राप्त करने पर जोर देते हैं, तो यहां बताया गया है कि पैर में खुद को कैसे गोली मारनी है:

एक नया उदाहरण सेट करने और वापस करने के लिए __missing__एक dictउपवर्ग पर लागू करें ।

पायथन 2.5 के बाद से यह दृष्टिकोण उपलब्ध (और प्रलेखित) किया गया है , और (विशेष रूप से मेरे लिए मूल्यवान है) यह एक सामान्य तानाशाह की तरह बहुत सुंदर प्रिंट करता है , बजाय एक स्वतः-स्वरूपित डिफ़ॉल्ट के बदसूरत मुद्रण के लिए:

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)() # retain local pointer to value
        return value                     # faster to return than dict lookup

(नोट self[key]असाइनमेंट के बाईं ओर है, इसलिए यहां कोई पुनरावृत्ति नहीं है।)

और कहें कि आपके पास कुछ डेटा है:

data = {('new jersey', 'mercer county', 'plumbers'): 3,
        ('new jersey', 'mercer county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'salesmen'): 62,
        ('new york', 'queens county', 'plumbers'): 9,
        ('new york', 'queens county', 'salesmen'): 36}

यहां हमारा उपयोग कोड है:

vividict = Vividict()
for (state, county, occupation), number in data.items():
    vividict[state][county][occupation] = number

और अब:

>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

आलोचना

इस प्रकार के कंटेनर की आलोचना यह है कि यदि उपयोगकर्ता एक कुंजी को छोड़ देता है, तो हमारा कोड चुपचाप विफल हो सकता है:

>>> vividict['new york']['queens counyt']
{}

और इसके अलावा अब हमारे पास हमारे डेटा में एक गलत वर्तनी वाली काउंटी होगी:

>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36},
              'queens counyt': {}}}

स्पष्टीकरण:

हम अपनी कक्षा का एक और नेस्टेड उदाहरण प्रदान कर रहे हैं Vividictजब भी कोई कुंजी एक्सेस की जाती है लेकिन गायब होती है तो हैं। (मान असाइनमेंट वापस करना उपयोगी है, क्योंकि यह हमें अतिरिक्त रूप से डिक्टेटर पर कॉल करने से रोकता है, और दुर्भाग्य से, हम इसे वापस नहीं कर सकते क्योंकि यह सेट किया जा रहा है।)

ध्यान दें, ये एक ही शब्दार्थ हैं जो सबसे अधिक उत्तर दिए गए हैं लेकिन कोड की आधी लाइनों में हैं - नोस्कोलो का कार्यान्वयन:

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

उपयोग का प्रदर्शन

नीचे सिर्फ एक उदाहरण है कि कैसे इस हुक को आसानी से मक्खी पर नेस्टेड तानाशाही संरचना बनाने के लिए इस्तेमाल किया जा सकता है। यह जल्दी से एक पदानुक्रमित वृक्ष संरचना बना सकता है जितनी गहराई से आप जाना चाहते हैं।

import pprint

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

d = Vividict()

d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)

कौन से आउटपुट:

{'fizz': {'buzz': {}},
 'foo': {'bar': {}, 'baz': {}},
 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

और आखिरी पंक्ति के रूप में, यह सुंदर रूप से और मैनुअल निरीक्षण के लिए प्रिंट करता है। लेकिन अगर आप अपने डेटा को नेत्रहीन रूप से निरीक्षण करना चाहते हैं, तो __missing__अपनी कक्षा का एक नया उदाहरण कुंजी पर लागू करना और उसे वापस करना बेहतर समाधान है।

अन्य विकल्प, इसके विपरीत:

dict.setdefault

हालांकि पूछने वाले को लगता है कि यह साफ नहीं है, मुझे यह Vividictअपने लिए बेहतर लगता है ।

d = {} # or dict()
for (state, county, occupation), number in data.items():
    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

और अब:

>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

एक गलत वर्तनी बिना किसी असफलता के विफल हो जाएगी, और खराब सूचना के साथ हमारे डेटा को अव्यवस्थित नहीं करेगी:

>>> d['new york']['queens counyt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'

इसके अतिरिक्त, मुझे लगता है कि छोरों में इस्तेमाल होने पर सेटडेफॉल्ट बहुत अच्छा काम करता है और आपको नहीं पता कि आपको चाबियों के लिए क्या मिलेगा, लेकिन दोहराव का उपयोग काफी बोझिल हो जाता है, और मुझे नहीं लगता कि कोई भी निम्नलिखित को रखना चाहेगा:

d = dict()

d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})

एक और आलोचना यह है कि सेटडेफॉल्ट को एक नए उदाहरण की आवश्यकता होती है चाहे इसका उपयोग किया जाए या नहीं। हालाँकि, पायथन (या कम से कम CPython) अप्रयुक्त और अप्रतिबंधित नए उदाहरणों को संभालने के बारे में स्मार्ट है, उदाहरण के लिए, यह स्मृति में स्थान का पुन: उपयोग करता है:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

एक ऑटो-डिफाइंड डिफ़ॉल्ट

यह एक साफ-सुथरा दिखने वाला कार्यान्वयन है, और एक स्क्रिप्ट में उपयोग जो आप उस डेटा का निरीक्षण नहीं कर रहे हैं जो लागू करने के लिए उपयोगी होगा __missing__:

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

लेकिन अगर आपको अपने डेटा का निरीक्षण करने की आवश्यकता है, तो उसी तरह से डेटा के साथ आबादी वाले ऑटो-डिफाइंड डिफाल्ड के परिणाम इस तरह दिखाई देते हैं:

>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint; 
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict 
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar': 
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function 
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>, 
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at 
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})

यह आउटपुट काफी अशुभ है, और परिणाम काफी अपठनीय हैं। आम तौर पर दिया गया समाधान मैन्युअल निरीक्षण के लिए पुन: पुन: रूपांतरित करने के लिए है। यह गैर-तुच्छ समाधान पाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है।

प्रदर्शन

अंत में, प्रदर्शन पर नजर डालते हैं। मैं तात्कालिकता की लागत को घटा रहा हूं।

>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747

प्रदर्शन के आधार पर, dict.setdefaultसबसे अच्छा काम करता है। मैं इसे उत्पादन कोड के लिए अत्यधिक अनुशंसा करता हूं, उन मामलों में जहां आप निष्पादन की गति के बारे में परवाह करते हैं।

यदि आपको संवादात्मक उपयोग के लिए इसकी आवश्यकता है (एक IPython नोटबुक में, शायद) तो प्रदर्शन वास्तव में मायने नहीं रखता है - किस मामले में, मैं आउटपुट की पठनीयता के लिए विविड के साथ जाऊंगा। AutoVivification ऑब्जेक्ट की तुलना में (जो __getitem__इसके बजाय उपयोग करता है __missing__, जो इस उद्देश्य के लिए बनाया गया था) यह कहीं बेहतर है।

निष्कर्ष

एक नया उदाहरण सेट करने और वापस करने के लिए __missing__एक उपवर्ग पर लागू dictकरना विकल्पों की तुलना में थोड़ा अधिक कठिन है लेकिन इसके फायदे हैं

  • आसान तात्कालिकता
  • आसान डेटा जनसंख्या
  • आसान डेटा देखना

और क्योंकि यह कम जटिल है और संशोधित करने की तुलना में अधिक प्रदर्शनकारी है __getitem__, इसलिए इसे उस पद्धति को प्राथमिकता दी जानी चाहिए।

फिर भी, इसमें कमियां हैं:

  • खराब लुकअप चुपचाप विफल हो जाएगा।
  • ख़राब लुक डिक्शनरी में रहेगा।

इस प्रकार मैं व्यक्तिगत रूप setdefaultसे अन्य समाधानों को पसंद करता हूं , और हर स्थिति में जहां मुझे इस तरह के व्यवहार की आवश्यकता है।


बहुत बढ़िया जवाब! वहाँ एक सीमित गहराई और एक के लिए एक पत्ती प्रकार निर्दिष्ट करने के लिए कोई रास्ता नहीं है Vividict? उदाहरण के लिए 3और listऐसे तानाशाहों के हुक्मरानों की एक बड़ी संख्या के साथ जो आबाद हो सकते हैं d['primary']['secondary']['tertiary'].append(element)। मैं प्रत्येक गहराई के लिए 3 अलग-अलग वर्गों को परिभाषित कर सकता हूं, लेकिन मुझे एक क्लीनर समाधान खोजना अच्छा लगेगा।
एरिक ड्यूमिनील

@EricDuminil d['primary']['secondary'].setdefault('tertiary', []).append('element')- ?? प्रशंसा के लिए धन्यवाद, लेकिन मुझे ईमानदार रहने दें - मैं वास्तव में कभी उपयोग नहीं करता __missing__- मैं हमेशा उपयोग करता हूं setdefault। मैं शायद अपने निष्कर्ष / परिचय अद्यतन करना चाहिए ...
हारून हॉल

@AaronHall सही व्यवहार है कोड को जरूरत पड़ने पर एक तानाशाह बनाना चाहिए। इस मामले में पिछले असाइन किए गए मान को ओवरराइड करके।
नेहमे

@AaronHall क्या आप मुझे यह समझने में मदद कर सकते हैं कि The bad lookup will remain in the dictionary.इस उपाय का उपयोग करने के बारे में मेरा क्या विचार है? बहुत सराहना की। Thx
नेहमे

@AaronHall इसके साथ समस्या setdefaultतब विफल होगी जब यह गहराई के दो स्तरों से अधिक हो। ऐसा लगता है कि पायथन में कोई संरचना वर्णित के रूप में वास्तविक जीवंतता की पेशकश नहीं कर सकती है। मुझे दो चरणों के लिए एक get_nestedऔर एक के लिए समझौता करना था , set_nestedजिसके लिए तानाशाही और नेस्टेड विशेषताओं की सूची के लिए एक संदर्भ स्वीकार करें।
नेहमे

188
class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

परिक्षण:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

आउटपुट:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

किसी को भी यह समस्या है जब वे 3.x अजगर पर चले गए? stackoverflow.com/questions/54622935/…
jason

@ सजन pickleपायथन संस्करणों के बीच भयानक है। आप जो डेटा रखना चाहते हैं, उसे स्टोर करने के लिए इसका उपयोग करने से बचें। इसे केवल कैश और सामान के लिए उपयोग करें, जिसे आप डंप कर सकते हैं और अपनी इच्छानुसार पुनर्जीवित कर सकते हैं। दीर्घकालिक भंडारण या क्रमांकन विधि के रूप में नहीं।
nosklo

आप इन वस्तुओं को संग्रहीत करने के लिए क्या उपयोग करते हैं? मेरे ऑटोविविफिकेशन ऑब्जेक्ट में सिर्फ पांडा डेटाफ्रेम और स्ट्रिंग हैं।
जेसन

@ हसन डेटा के आधार पर, मुझे JSON, csv फ़ाइलों, या यहां तक ​​कि sqliteइसे संग्रहीत करने के लिए एक डेटाबेस का उपयोग करना पसंद है।
nosklo

30

सिर्फ इसलिए कि मैंने इस एक को छोटा नहीं देखा, यहाँ एक तानाशाही है जो आपको पसंद है, कोई पसीना नहीं है:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

2
@wberry: वास्तव में आप सभी की जरूरत है yodict = lambda: defaultdict(yodict)
मार्टिउ

1
स्वीकृत संस्करण एक उपवर्ग है dict, इसलिए पूरी तरह से समकक्ष होने के लिए हमें x = Vdict(a=1, b=2)काम करने की आवश्यकता होगी ।
wberry

@wberry: स्वीकार किए गए उत्तर में क्या है, इसके बावजूद, dictओपी द्वारा बताई गई आवश्यकता का उपवर्ग नहीं होने के कारण, जिन्होंने केवल उन्हें लागू करने के लिए "सबसे अच्छा तरीका" पूछा - और इसके अलावा, यह नहीं करना चाहिए / नहीं करना चाहिए। पाइथन में वैसे भी बहुत कुछ।
मार्टिउ

24

आप एक YAML फ़ाइल बना सकते हैं और इसे PyYaml का उपयोग करके पढ़ सकते हैं ।

चरण 1: एक YAML फ़ाइल बनाएँ, "Employment.yml":

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

चरण 2: इसे पायथन में पढ़ें

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

और अब my_shnazzy_dictionaryआपके सभी मूल्य हैं। यदि आपको मक्खी पर ऐसा करने की आवश्यकता है, तो आप एक स्ट्रिंग के रूप में YAML बना सकते हैं और उस में फ़ीड कर सकते हैं yaml.safe_load(...)


4
YAML मेरी निश्चित रूप से बहुत से नेस्टेड डेटा (और कॉन्फ़िगरेशन फ़ाइलें, डेटाबेक मॉकअप, आदि ...) के बहुत सारे इनपुट के लिए मेरी पसंद है। यदि ओपी अतिरिक्त फ़ाइलों के आसपास नहीं चाहता है, तो बस कुछ फ़ाइल में एक नियमित पायथन स्ट्रिंग का उपयोग करें और इसे YAML के साथ पार्स करें।
किमीलेवन

YAML स्ट्रिंग्स बनाने पर अच्छा बिंदु: यह बार-बार "टेम्पोफाइल" मॉड्यूल का उपयोग करने की तुलना में बहुत क्लीनर दृष्टिकोण होगा।
पीट

18

चूंकि आपके पास एक स्टार-स्कीमा डिज़ाइन है, आप इसे एक संबंधपरक तालिका की तरह और एक शब्दकोश की तरह कम संरचना देना चाह सकते हैं।

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

SQL ओवरहेड्स के बिना डेटा वेयरहाउस जैसी डिज़ाइन बनाने के लिए इस तरह की चीज़ बहुत आगे बढ़ सकती है।


14

यदि घोंसले के शिकार का स्तर छोटा है, तो मैं इसके लिए उपयोग करता हूं collections.defaultdict:

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

का उपयोग करते हुए defaultdictगन्दा का एक बहुत इस तरह टाल setdefault(), get()आदि


+1: डिफ़ॉल्ट रूप से अजगर के लिए मेरा सर्वकालिक पसंदीदा जोड़ है। कोई और .setdefault ()!
जॉन फोहि

8

यह एक ऐसा कार्य है जो मनमाना गहराई का एक नेस्टेड शब्दकोश देता है:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

इसे इस तरह उपयोग करें:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

इस तरह से कुछ के साथ सब कुछ के माध्यम से बदलो:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

यह प्रिंट करता है:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

आप अंततः इसे बनाना चाहते हैं ताकि नए आइटम को तानाशाही में नहीं जोड़ा जा सके। इन सभी defaultdictको सामान्य dictएस में बदलना आसान है ।

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

7

मुझे setdefaultकाफी उपयोगी लगता है; यह जाँचता है कि क्या कोई कुंजी मौजूद है और यदि नहीं तो उसे जोड़ता है:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefaultहमेशा प्रासंगिक कुंजी लौटाता है, इसलिए आप वास्तव dमें जगह में ' ' के मूल्यों को अपडेट कर रहे हैं ।

जब यह पुनरावृत्ति की बात आती है, तो मुझे यकीन है कि आप एक जनरेटर को आसानी से लिख सकते हैं यदि कोई पहले से ही पायथन में मौजूद नहीं है:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

मैं इस समाधान की तरह लेकिन जब मैं कोशिश: count.setdefault (क, {}) SetDefault (ख, {}) SetDefault (ग, 0) + = 1 मैं "संवर्धित कार्य के लिए अवैध रूप से अभिव्यक्ति" मिलता
dfrankow

6

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

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

यह सिर्फ एक सरल उदाहरण है। आप राज्यों, काउंटियों और नौकरी के शीर्षकों के लिए अलग-अलग तालिकाओं को परिभाषित कर सकते हैं।


5

collections.defaultdictएक नेस्टेड तानाशाही बनाने के लिए उप-वर्गीकृत किया जा सकता है। फिर उस कक्षा में कोई उपयोगी पुनरावृत्ति विधियाँ जोड़ें।

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

1
यह वह उत्तर है जो मैं देख रहा था, उसके करीब आता है। लेकिन आदर्श रूप से सभी प्रकार के सहायक कार्य होंगे, जैसे walk_keys () या ऐसा। मुझे आश्चर्य है कि ऐसा करने के लिए मानक पुस्तकालयों में कुछ भी नहीं है।
YGA

4

"अप्रिय प्रयास / ब्लॉक को पकड़ने" के लिए:

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

पैदावार

{'key': {'inner key': {'inner inner key': 'value'}}}

आप इसका उपयोग अपने फ्लैट शब्दकोश प्रारूप से संरचित प्रारूप में बदलने के लिए कर सकते हैं:

fd = {('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v


4

defaultdict() आपका दोस्त है!

दो आयामी शब्दकोश के लिए आप कर सकते हैं:

d = defaultdict(defaultdict)
d[1][2] = 3

अधिक आयामों के लिए आप कर सकते हैं:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

यह उत्तर केवल तीन स्तरों के लिए सबसे अच्छा काम करता है। मनमाने स्तर के लिए, इस उत्तर पर विचार करें
एक्यूमेनस

3

अपने नेस्टेड शब्दकोश पर आसान पुनरावृत्ति के लिए, बस एक साधारण जनरेटर क्यों नहीं लिखें?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

तो, अगर आपके पास अपना संकलित नेस्टेड शब्दकोश है, तो उस पर चलना सरल हो जाता है:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

जाहिर है कि आपका जनरेटर डेटा के जो भी प्रारूप आपके लिए उपयोगी हो सकता है।

आप पेड़ को पढ़ने के लिए पकड़ने वाले ब्लॉक का उपयोग क्यों कर रहे हैं? यह काफी आसान है (और शायद सुरक्षित) कि क्या एक कुंजी एक पुनःप्राप्त करने की कोशिश करने से पहले मौजूद है। गार्ड क्लॉज का उपयोग करने वाला एक कार्य इस प्रकार हो सकता है:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

या, कुछ हद तक एक क्रिया विधि है, प्राप्त विधि का उपयोग करना है:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

लेकिन कुछ हद तक रसीले तरीके के लिए, आप एक संग्रह का उपयोग करके देखना चाह सकते हैं ।defaultdict , जो कि पायथन 2.5 के बाद से मानक पुस्तकालय का हिस्सा है।

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

मैं यहां आपके डेटा संरचना के अर्थ के बारे में धारणा बना रहा हूं, लेकिन जो आप वास्तव में करना चाहते हैं, उसके लिए इसे समायोजित करना आसान होना चाहिए।


2

मुझे इसे कक्षा में लपेटने और लागू करने का विचार पसंद है __getitem__और __setitem__इस तरह उन्होंने एक सरल क्वेरी भाषा लागू की:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

यदि आप फैंसी प्राप्त करना चाहते हैं तो आप कुछ ऐसा भी लागू कर सकते हैं:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

लेकिन ज्यादातर मुझे लगता है कि इस तरह की बात लागू करने के लिए वास्तव में मजेदार होगी: डी


मुझे लगता है कि यह एक बुरा विचार है - आप कभी भी चाबियों के वाक्यविन्यास का अनुमान नहीं लगा सकते। आप अभी भी गेटिटेम और सेटिटेम को ओवरराइड करेंगे लेकिन उन्हें ट्यूपल्स ले लें।
YGA

3
@YGA आप शायद सही हैं, लेकिन इस तरह की मिनी भाषाओं को लागू करने के बारे में सोचने में मज़ा आता है।
एरोन मेनेपा

1

जब तक आपका डेटासेट बहुत छोटा नहीं रहेगा, तब तक आप रिलेशनल डेटाबेस का उपयोग करने पर विचार कर सकते हैं। यह वैसा ही करेगा जैसा आप चाहते हैं: राज्य, काउंटी, व्यवसाय, या इनमें से किसी भी संयोजन द्वारा गणनाओं को जोड़ना आसान बनाना, गिनती के सबसेट का चयन करना और यहां तक ​​कि कुल गणनाओं को पूरा करना।


1
class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

उदाहरण:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

संपादित करें: अब वाइल्ड कार्ड ( None), और एकल मान के साथ क्वेरी करते समय शब्दकोशों को वापस करना अन्यथा।


सूची क्यों लौटाएं? लगता है कि इसे या तो एक शब्दकोश वापस करना चाहिए (ताकि आपको पता चले कि प्रत्येक संख्या क्या दर्शाती है) या एक राशि (क्योंकि यह सब आप वास्तव में सूची के साथ कर सकते हैं)।
बेन ब्लैंक

0

मेरी भी ऐसी ही बात है। मेरे पास बहुत सारे मामले हैं जहाँ मैं करता हूँ:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

लेकिन कई स्तर गहरे जा रहे हैं। यह ".get (आइटम, {})" है कि यह कुंजी है क्योंकि यह एक और शब्दकोश बना देगा यदि कोई पहले से नहीं है। इस बीच, मैं इससे बेहतर तरीके से निपटने के तरीकों के बारे में सोच रहा हूं। अभी, वहाँ बहुत कुछ है

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

इसके बजाय, मैंने बनाया:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

यदि आप ऐसा ही करते हैं तो इसका प्रभाव पड़ता है:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

बेहतर? मुझे ऐसा लगता है।


0

आप लैम्ब्डा और डिफाल्ड डिसीट में पुनरावर्तन का उपयोग कर सकते हैं, नामों को परिभाषित करने की कोई आवश्यकता नहीं है:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

यहाँ एक उदाहरण है:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})

0

मैं इस फ़ंक्शन का उपयोग करता था। इसकी सुरक्षित, त्वरित, आसानी से बनाए रखने योग्य है।

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

उदाहरण :

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.