Вкладений вирок за замовчуванням


129

Чи є спосіб, щоб вирок за замовчуванням також був за замовчуванням? (тобто нескінченний рекурсивний вирок за замовчуванням?)

Я хочу вміти:

x = defaultdict(...stuff...)
x[0][1][0]
{}

Так, я можу зробити x = defaultdict(defaultdict), але це лише другий рівень:

x[0]
{}
x[0][0]
KeyError: 0

Є рецепти, які можуть це зробити. Але чи можна це зробити просто, використовуючи звичайні аргументи за замовчуванням?

Зауважте, це запитання, як зробити рекурсивний вирок за замовчуванням нескінченного рівня, щоб він відрізнявся від Python: вирок за замовчуванням за замовчуванням? , як було зробити дворівневий вирок за замовчуванням.

Я, мабуть, просто в кінцевому підсумку використовую схему згустку , але коли зрозумів, що не знаю, як це зробити, мене зацікавило.



2
Не дуже ... додав інформацію до запитання, щоб вказати, чому. Хоча це корисне питання.
Корлі Бригман

Відповіді:


168

Для довільної кількості рівнів:

def rec_dd():
    return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Звичайно, ви могли б це зробити і з лямбда, але я вважаю лямбди менш читабельними. У будь-якому випадку це виглядатиме так:

rec_dd = lambda: defaultdict(rec_dd)

1
Справді прекрасний приклад, дякую. Чи можете ви, будь ласка, поширити це на випадок, що дані завантажуються з json у вирок за замовчуванням?
Девід Белоград

4
Одна примітка. Якщо ви намагаєтеся використовувати цей код під час маринування lambda, не вийде.
В’ячеслав Кондратюк

166

Інші відповіді тут розповідають вам про те, як створити вміст, defaultdictякий містить "нескінченно багато" defaultdict, але вони не в змозі вирішити те, що, на мою думку, можливо, було вашою первинною потребою, а саме - мати двобічний вирок за замовчуванням.

Можливо, ви шукали:

defaultdict(lambda: defaultdict(dict))

Причини, чому ви можете віддати перевагу цій конструкції:

  • Це більш чітке, ніж рекурсивне рішення, і тому, ймовірно, більш зрозуміле для читача.
  • Це дає змогу "аркуш" листа defaultdictбути чимось іншим, ніж словник, наприклад:: defaultdict(lambda: defaultdict(list))абоdefaultdict(lambda: defaultdict(set))

3
дефолт (лямбда: дефолт (список)) Правильна форма?
Yuvaraj Loganathan

Ой, так, lambdaформа правильна - тому що defaultdict(something)повертає словниковий об’єкт, але defaultdictочікує дзвінка! Дякую!
Кріс В.

4
Це було позначене як можливий повтор іншого питання ... але це не було моїм оригінальним запитанням. Я знав, як створити дворівневий вирок за замовчуванням; те, чого я не знав, - це зробити його рекурсивним. Відповідь на це питання, по суті, схожий на stackoverflow.com/questions/5029934 / ...
Корлі Brigman

Одне недоліки лямбда-підходу полягає в тому, що предмети, які він створює, не можуть бути замариновані ... але це можна обійти, dict(result)
відправивши

54

Для цього є чудовий трюк:

tree = lambda: defaultdict(tree)

Тоді ви можете створити свій xз x = tree().


22

Подібно до рішення BrenBarn, але не містить імені змінної treeдвічі, тому вона працює навіть після змін у словнику змінної:

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

Тоді ви можете створити кожен новий за xдопомогою x = tree().


Для defверсії ми можемо використовувати область закриття функції, щоб захистити структуру даних від недоліку, коли існуючі екземпляри перестають працювати, якщо treeім'я відновлюється. Це виглядає приблизно так:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()

4
мені доведеться подумати над цим (це трохи складніше). але я думаю, ваша думка полягає в тому, що якщо зробити x = tree (), але тоді хтось пізніше піде і робить дерево = None, це все одно буде працювати, і це не буде?
Корлі Бригман

11

Я також запропонував би більшу реалізацію в стилі OOP, яка підтримує нескінченне гніздування, а також правильно відформатовану форму repr.

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)

    def __repr__(self):
        return repr(dict(self))

Використання:

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']

print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}

1
Акуратно! Я додав транзитну пересилку з *argsі **kwargsяка дозволяє йому функціонувати Сподобалися defaultdict, а саме створити Dict з іменованими аргументами. Це корисно для проїзду NestedDefaultDictвjson.load
Ciprian Tomoiagă

0

ось рекурсивна функція для перетворення рекурсивного диктату за замовчуванням у звичайний

def defdict_to_dict(defdict, finaldict):
    # pass in an empty dict for finaldict
    for k, v in defdict.items():
        if isinstance(v, defaultdict):
            # new level created and that is the new value
            finaldict[k] = defdict_to_dict(v, {})
        else:
            finaldict[k] = v
    return finaldict

defdict_to_dict(my_rec_default_dict, {})

0

Я на основі цього з Андріївського відповіді тут. Якщо ви хочете завантажити дані з json чи наявного дикта в рішення про замовчування в нестер, див. Цей приклад:

def nested_defaultdict(existing=None, **kwargs):
    if existing is None:
        existing = {}
    if not isinstance(existing, dict):
        return existing
    existing = {key: nested_defaultdict(val) for key, val in existing.items()}
    return defaultdict(nested_defaultdict, existing, **kwargs)

https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.