Подтвердить что ты не робот

Python dict.get() с многомерным dict

У меня многомерный dict, и я бы хотел получить значение с помощью пары key: key и вернуть "NA" , если первый ключ не существует. Все поддиты имеют одинаковые ключи.

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

Я знаю, что могу использовать d.get('c','NA'), чтобы получить субдиск, если он существует, и вернуть "NA" в противном случае, но мне действительно нужно только одно значение из поддикта. Я бы хотел сделать что-то вроде d.get('c['j']','NA'), если это существовало.

В данный момент я просто проверяю, существует ли ключ верхнего уровня, а затем присваивает субъекту переменную, если она существует, или "NA" , если нет. Тем не менее, я делаю это примерно в 500 тыс. Раз, а также извлекаю/генерирую другую информацию о каждом ключе верхнего уровня из другого места, и я пытаюсь немного ускорить это.

4b9b3361

Ответ 1

Как насчет

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

?

Если не все субдикции имеют ключ j, то

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

 

Чтобы срубить идентичные созданные объекты, вы можете придумать что-то вроде

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

 

Такая же идея с использованием 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}
            })

Ответ 2

Здесь простой и эффективный способ сделать это с обычными словарями, вложенное произвольное количество уровней:

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

Это также можно сделать рекурсивно:

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

Хотя этот способ сделать это не так эффективен, как первый.

Ответ 3

Вместо иерархии вложенных объектов dict вы можете использовать один словарь, ключи которого представляют собой кортеж, представляющий путь через иерархию.

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

Доступ к этому способу выглядит примерно на 15% быстрее. Вы все равно можете использовать метод get со значением по умолчанию:

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

Ответ 4

Другой способ получить многомерный пример dict (дважды используйте метод get)

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