Предотвращение ошибок хранения ключей во вложенном словаре (Python)

Вступление

Следующий словарь имеет три уровня ключей, а затем значение.

d = {
    1:{
        'A':{
            'i': 100,
            'ii': 200
            }, 
        'B':{
            'i': 300
            }
        }, 
    2:{
        'A':{
            'ii': 500
            }
        }
    }

Примеры, которые необходимо добавить.

d[1]['B']['ii'] = 600      # OK
d[2]['C']['iii'] = 700     # Keyerror on 'C'
d[3]['D']['iv'] = 800      # Keyerror on 3

Постановка задачи

Я хотел создать код, который бы создавал необходимые вложенные ключи и избегал ошибок ключей.

Решение 1

Первое решение, которое я придумал, было:

def NewEntry_1(d, lv1, lv2, lv3, value):
    if lv1 in d:
        if lv2 in d:
            d[lv1][lv2][lv3] = value
        else:
            d[lv1][lv2] = {lv3: value}
    else:
        d[lv1] = {lv2: {lv3: value}}

Кажется законным, но встраивание этого в куски кода порядка заставило его ошеломить. Я исследовал Stackru для других решений и читал функции get() и setdefault().

Решение 2

Существует множество материалов о get() и setdefault(), но не так много о вложенных словарях. В конечном итоге я смог придумать:

def NewEntry_2(d, lv1, lv2, lv3, value):
    return d.setdefault(lv1, {}).setdefault(lv2,{}).setdefault(lv3, value)

Это одна строка кода, поэтому нет необходимости делать это функцией. Легко модифицируется для включения операций:

d[lv1][lv2][lv3] = d.setdefault(lv1, {}).setdefault(lv2,{}).setdefault(lv3, 0) + value

Кажется идеальным?

Вопрос

При добавлении большого количества записей и выполнении многих модификаций вариант 2 лучше, чем вариант 1? Или я должен определить функцию 1 и вызвать ее? Ответы, которые я ищу, должны учитывать скорость и / или вероятность ошибок.

Примеры

NewEntry_1(d, 1, 'B', 'ii', 600)
# output = {1: {'A': {'i': 100, 'ii': 200}, 'B': {'i': 300, 'ii': 600}}, 2: {'A': {'ii': 500}}}

NewEntry_1(d, 2, 'C', 'iii', 700)
# output = {1: {'A': {'i': 100, 'ii': 200}, 'B': {'i': 300, 'ii': 600}}, 2: {'A': {'ii': 500}, 'C': {'iii': 700}}}

NewEntry_1(d, 3, 'D', 'iv', 800)
# output = {1: {'A': {'i': 100, 'ii': 200}, 'B': {'i': 300, 'ii': 600}}, 2: {'A': {'ii': 500}, 'C': {'iii': 700}}, 3: {'D': {'iv': 800}}}

Больше фона

Я бизнес-аналитик, исследующий использование Python для создания Graph DB, который помог бы мне с очень специфическим анализом. Структура словаря используется для описания влияния одного узла на одного из его соседей:

  • lv1 - это Узел Из
  • lv2 - это узел To
  • lv3 - это итерация
  • значение Влияние (в%)

На первой итерации Node 1 оказывает непосредственное влияние на Node 2. На второй итерации Node 1 влияет на все Nodes, на которые влияет Node 2.

Мне известны пакеты, которые могут помочь мне в этом (networkx), но я пытаюсь понять Python/GraphDB, прежде чем я хочу начать их использовать.

1 ответ

Решение

Что касается вложенных словарей, вы должны взглянуть на defaultdict, Его использование избавит вас от накладных расходов при вызове функций. Вложенные defaultdict строительство курортов в lambda функции для их заводов по умолчанию:

d = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))  # new, shiny, empty
d[1]['B']['ii'] = 600      # OK
d[2]['C']['iii'] = 700     # OK
d[3]['D']['iv'] = 800      # OK

Обновление: полезный трюк, чтобы знать, чтобы создать глубоко вложенный defaultdict является следующим:

def tree():
    return defaultdict(tree)

d = tree()  
# now any depth is possible
# d[1][2][3][4][5][6][7][8] = 9
Другие вопросы по тегам