3

Say I have two objects like this:

d={'a':{'z':1,'y':2},'b':{'z':0,'y':4}}
k=('a','y')

How can I use these two objects to get:

>>> d['a']['y'] 
2

I need it to be dynamic so this won't work:

d[k[0]][k[1]]

because I am not always sure of the dictionary's nesting depth. k could have one element or several.

LMc
  • 12,577
  • 3
  • 31
  • 43
  • I think we might need a little more context to answer this one. What does the actual data look like, and what are you trying to do with it? – Jeff Jun 28 '16 at 17:09
  • 1
    @JeffL. I disagree, I think the problem is pretty clearly stated :) – en_Knight Jun 28 '16 at 17:10

2 Answers2

7
try:                  
    # python 3 maybe?
    from functools import reduce                  

except ImportError:
    # assume python 2...
    pass                  

reduce(dict.__getitem__, k, d)

A bit of explanation was requested: reduce(fn, [a1, a2, a3...], b) translates into: fn(... fn(fn(fn(b, a1), a2), a3) ...). So, it pairs some "accumulator" value with consecutive values from the sequence, passes that to the function passed in and then uses that to update the "accumulator" value.

So our "accumulator" value is top-level dictionary, which we pass as the first ("self") argument into dictionary method __getitem__, which implements subscript operator.

As noted in the comments below you can use operator.getitem in place dict.__getitem__ to generalize to other datatypes that support indexing such as lists, tuples, strings...

Adam Sosnowski
  • 1,126
  • 9
  • 7
  • What if I'm in python3? – en_Knight Jun 28 '16 at 17:11
  • 2
    `reduce` has been buried in `functools` in python 3. – Adam Sosnowski Jun 28 '16 at 17:12
  • 1
    Consider adding that to the answer, since this question isn't tagged either way. Also, I upvoted because this is correct, but consider providing a teensy bit of explanation about what's going on here, as the one line is not too instructive to anyone not familiar with python's functional tools – en_Knight Jun 28 '16 at 17:13
  • 1
    Beautiful solution, though I would rather use `lambda dic, key: dic[key]` for aesthetic reasons. – Eli Korvigo Jun 28 '16 at 17:13
  • Do you have any suggests for how you might increment that object? Say to get something like d['a']['y']+=1? – LMc Jun 28 '16 at 17:14
  • 1
    @EliKorvigo: Beauty is in the eye of the beholder. I find `lambda dic, key: dic[key]` to be ugly. – Steven Rumbalski Jun 28 '16 at 17:18
  • 1
    What about `operator.getitem`? – Eli Korvigo Jun 28 '16 at 17:23
  • 1
    Actually, @EliKorvigo suggestion is not purely aesthetic, but generalizes the solution to other nested structures that are not dictionaries but support subscripting, e.g.: lists. I'll update solution accordingly. – Adam Sosnowski Jun 28 '16 at 17:29
  • 2
    @LMc: To increment the value in the innermost dict do `*k, last_k = k`. And then do `reduce(dict.__getitem__, k, d)[last_k] += 1`. This will work at any level of nesting. Given `d = {'a':{'b':{'c':1}}, 'd':1}` it will work where `k = ('a', 'b', 'c')` and where`k = ('d',)`. – Steven Rumbalski Jun 28 '16 at 17:31
1

I think Adam's solution is by far the cleaner one, but because there's always an imperative alternative to the functional approach, we could also solve this recursively:

 def get(d,k):
     if len(k) == 1:
             return d[k[0]]
     return get(d[k[0]], k[1:])

Similarly with for loops:

result = d.copy()
for key in k:
    result = result[key]

Noting that loops don't have their own scope in python

en_Knight
  • 5,301
  • 2
  • 26
  • 46