2013-04-14 25 views
11

Ho un dettato multidimensionale e mi piacerebbe essere in grado di recuperare un valore con una chiave: la coppia di chiavi e restituire 'NA' se la prima chiave non esiste . Tutti i sottotit hanno le stesse chiavi.Python dict.get() con dict multidimensionale

d = { 'a': {'j':1,'k':2}, 
     'b': {'j':2,'k':3}, 
     'd': {'j':1,'k':3} 
    } 

So che posso usare d.get('c','NA') per ottenere il sub-dict se esiste e ritorno 'NA' il contrario, ma ho davvero solo bisogno di un valore dal sub-dict. Mi piacerebbe fare qualcosa come d.get('c['j']','NA') se esistesse.

In questo momento sto solo verificando se esiste la chiave di primo livello e quindi assegnando il valore secondario a una variabile se esiste o "NA" se non lo è. Tuttavia, lo faccio circa 500k volte e anche recuperando/generando altre informazioni su ogni chiave di livello superiore da un'altra parte, e sto cercando di accelerare un po 'questo.

risposta

20

Come su

d.get('a', {'j': 'NA'})['j'] 

?

Se non tutti i subdicts hanno una chiave j, quindi

d.get('a', {}).get('j', 'NA') 

 

per ridurre oggetti identici creati, è possibile inventare qualcosa di simile

class DefaultNASubdict(dict): 
    class NADict(object): 
     def __getitem__(self, k): 
      return 'NA' 

    NA = NADict() 

    def __missing__(self, k): 
     return self.NA 

nadict = DefaultNASubdict({ 
       'a': {'j':1,'k':2}, 
       'b': {'j':2,'k':3}, 
       'd': {'j':1,'k':3} 
      }) 

print nadict['a']['j'] # 1 
print nadict['b']['j'] # 2 
print nadict['c']['j'] # NA 

 

Stessa idea utilizzando defaultdict:

import collections 

class NADict(object): 
    def __getitem__(self, k): 
     return 'NA' 

    @staticmethod 
    def instance(): 
     return NADict._instance 

NADict._instance = NADict() 


nadict = collections.defaultdict(NADict.instance, { 
       'a': {'j':1,'k':2}, 
       'b': {'j':2,'k':3}, 
       'd': {'j':1,'k':3} 
      }) 
+0

un'occhiata a 'collections.defaultdict' per un'implementazione già prevista, vale a dire' defaultdict (lambda: defaultdict (lambda: 'NA')) ' – mtadd

+0

Certo, ma è ancora necessario un' NADict' e una funzione che restituisce un'istanza condivisa di esso. Aggiungerò un esempio. –

+0

@mtadd: l'idea era di non creare un nuovo dict/defaultdict su ogni mislookup. –

2

Piuttosto che una gerarchia di oggetti annidati dict, è possibile utilizzare un dizionario le cui chiavi sono una tupla che rappresenta un percorso attraverso la gerarchia.

In [34]: d2 = {(x,y):d[x][y] for x in d for y in d[x]} 

In [35]: d2 
Out[35]: 
{('a', 'j'): 1, 
('a', 'k'): 2, 
('b', 'j'): 2, 
('b', 'k'): 3, 
('d', 'j'): 1, 
('d', 'k'): 3} 

In [36]: timeit [d[x][y] for x,y in d2.keys()] 
100000 loops, best of 3: 2.37 us per loop 

In [37]: timeit [d2[x] for x in d2.keys()] 
100000 loops, best of 3: 2.03 us per loop 

L'accesso a questo modo sembra essere circa il 15% più veloce. È comunque possibile utilizzare il metodo get con un valore predefinito:

In [38]: d2.get(('c','j'),'NA') 
Out[38]: 'NA' 
4

Ecco un modo semplice ed efficace per farlo con dizionari ordinarie, annidato un numero arbitrario di livelli:

d = {'a': {'j': 1, 'k': 2}, 
    'b': {'j': 2, 'k': 3}, 
    'd': {'j': 1, 'k': 3}, 
    } 

def chained_get(dct, *keys): 
    SENTRY = object() 
    def getter(level, key): 
     return 'NA' if level is SENTRY else level.get(key, SENTRY) 
    return reduce(getter, keys, dct) 

print chained_get(d, 'a', 'j') # 1 
print chained_get(d, 'b', 'k') # 3 
print chained_get(d, 'k', 'j') # NA 

Potrebbe anche essere fatto in modo ricorsivo:

def chained_get(dct, *keys): 
    SENTRY = object() 
    def getter(level, keys): 
     return (level if keys[0] is SENTRY else 
        'NA' if level is SENTRY else 
         getter(level.get(keys[0], SENTRY), keys[1:])) 
    return getter(dct, keys+(SENTRY,)) 

Anche se questo modo di farlo non è altrettanto efficiente come il primo.

0

Un altro modo per ottenere ad esempio dict multidimensionale (uso metodo get due volte)

d.get('a', {}).get('j')