Как вставить на «уровне» структуру данных dict / list? - {'foo': 'bar', 'more': [{'foo': None}]} - PullRequest
0 голосов
/ 11 марта 2020

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

# Level 0
{ 'foo': 'bar', 'more': 
  [   # Level 1
      { 'foo': 'can', 'more': [    # Level 2
                                   { 'foo': 'haz', 'more': [] }
                              ]
      },
      { 'foo': 'baz', 'more': [    # Level 2
                                   { 'foo': None, 'more': [] }
                              ]
      }
  ]
}

Работа над функцией для вставки на определенном уровне. Если значение foo равно None, введите value, в противном случае вставьте новый «брат», т. Е. Новый dict на том же уровне. Помимо 0-го уровня, все находится в пределах list, поэтому должен быть append способный.


Попытка:

def traverse_to_level(obj, level):
    if obj['_level'] == level:
        return obj

    for _obj in obj['block']:
        found = traverse_to_level(_obj, level)
        if found is not None:
            return found

    return None


def set_obj(top_obj, value, level):
    obj = traverse_to_level(top_obj, level)
    if obj is None:
        directive = traverse_to_level(top_obj, level - 1)
    assert obj is not None
    if obj['foo'] is None:
        obj['foo'] = value
    elif obj['_level'] < level:
        obj['more'].append({'foo': value, 'more': [], '_level': level})
    else:
        obj = traverse_to_level(top_obj, level - 1)
        obj['more'].append({'foo': value, 'more': [], '_level': level})

    return obj

Что такое правильный путь для обхода структуры, подобной этой, и обновления ее на месте с помощью level?


РЕДАКТИРОВАТЬ: еще один пример, показывающий больше, чем просто 'foo' на каждом объекте. Для упрощения: если ввод начинается с #, то его следует добавить к alpha, в противном случае beta. Если любой из них уже заполнен, вставьте соседний (т. Е. Новый брат {'alpha': None, 'beta': None, 'more': []}).

def make_ab(alpha=None, beta=None, more=None):
    return {
        'alpha': alpha,
        'beta': beta,
        'more': more or []
    }


def parse(arguments):
    level, last_level_change, top_d = 0, -1, make_ab()

    for idx, arg in enumerate(arguments):
        if arg == '#{':
            level += 1
        elif arg in frozenset(('#d', '##dog')):
            level += 1
            last_level_change = idx
        elif arg == '#}':
            level -= 1
        elif idx == last_level_change - 1:
            set_obj(top_d, arg, level, 'alpha')
        elif idx == last_level_change - 2:
            set_obj(top_d, arg, level, 'beta')
        else:
            set_obj(top_d, arg, level,
                    'beta' if arg.startswith('##')
                    else 'alpha')
    return top_d

Использование, намекает на ожидаемую иерархию через отступ:

actual = parse(
    ('#d',
        'definite',
        '##foo_alpha_5', 'nice_beta',
        '##name_2', 'amazing_beta',
        '##dog',
          '##fancy_name', 'fancy_BETA_f',
          '##one_AL_PHA_of', 'I_C_U_B_ETA',
        '#}',
    '#}')
)

И это ожидаемый результат:

{
    'alpha': 'definite',
    'beta': None,
    'more': [
        {
            'alpha': '##foo_alpha_5',
            'beta': 'nice_beta',
            'more': []
        },
        {
            'alpha': '##name_2',
            'beta': 'amazing_beta',
            'more': [
                {
                    'alpha': '##fancy_name',
                    'beta': 'fancy_BETA_f',
                    'more': []
                },
                {
                    'alpha': '##one_AL_PHA_of',
                    'beta': 'I_C_U_B_ETA',
                    'more': []
                }
            ]
        }
    ]
}

Ответы [ 2 ]

0 голосов
/ 20 марта 2020

Поскольку вы имеете дело с вложенными списками, вам также необходимо указать индекс на каждом уровне, чтобы сформировать какой-то путь. Как вы указали в своем примере, есть два разных списка на уровне 2, поэтому на уровне 1 вам нужно выбирать между первым или вторым диктом. Для этого вы можете использовать путь в форме списка или кортежа: например, (1, 0) означает, что на уровне 1 выберите второй диктовку, а на уровне 2 выберите первый диктовку. Это можно записать в виде функции:

from functools import reduce

def get_nested_dict(obj, path):
    return reduce(lambda o, n: o['more'][n], path, obj)

Затем протестируйте ее на данных примера:

print(get_nested_dict(test_obj, (0, 0)))  # foo: haz
print(get_nested_dict(test_obj, (1, 0)))  # foo: None

Теперь для вставки нам нужно получить список переноса, чтобы мы могли вставить Диктовка брата, если клавиша 'foo' уже занята. Для этого мы можем немного изменить указанную выше функцию:

def get_nested_list(obj, path):  # assert len(path) > 0
    return reduce(lambda o, n: o[n]['more'], path, obj['more'])

Затем мы можем построить функцию вставки поверх этого:

def insert_into(obj, path, value):
    lst = get_nested_list(obj, path[:-1])
    try:
        dct = lst[path[-1]]
    except IndexError:
        lst.append({'foo': value, 'more': []})  # create new dict
    else:
        if dct['foo'] is None:
            dct['foo'] = value
        else:
            lst.append({'foo': value, 'more': []})  # create new dict

Мы можем проверить это на данных примера:

pprint(test_obj, sort_dicts=False)
insert_into(test_obj, (0, 0), 'new')
pprint(test_obj, sort_dicts=False)
insert_into(test_obj, (1, 0), 'new')
pprint(test_obj, sort_dicts=False)

Что печатает следующее:

{'foo': 'bar',
 'more': [{'foo': 'can', 'more': [{'foo': 'haz', 'more': []}]},
          {'foo': 'baz', 'more': [{'foo': None, 'more': []}]}]}
{'foo': 'bar',
 'more': [{'foo': 'can',
           'more': [{'foo': 'haz', 'more': []}, {'foo': 'new', 'more': []}]},
          {'foo': 'baz', 'more': [{'foo': None, 'more': []}]}]}
{'foo': 'bar',
 'more': [{'foo': 'can',
           'more': [{'foo': 'haz', 'more': []}, {'foo': 'new', 'more': []}]},
          {'foo': 'baz', 'more': [{'foo': 'new', 'more': []}]}]}
0 голосов
/ 14 марта 2020

Как насчет чего-то вроде:

def set_obj(top_obj, value, level):
    if level == 1:
        more = top_obj['more']
        hasnt_none = True
        for n in more:
            if n['foo'] is None:
                n['foo'] = value
                hasnt_none = False
        if hasnt_none:
            more.append({'foo': value, 'more': []})
    else:
        for n in top_obj['more']:
            set_obj(n, value, level-1)

? Тест:

from pprint import pprint
set_obj(data, 'VALUE', 2)
pprint(data)

Вывод:

{'foo': 'bar',
 'more': [{'foo': 'can',
           'more': [{'foo': 'haz', 'more': []}, {'foo': 'VALUE', 'more': []}]},
          {'foo': 'baz', 'more': [{'foo': 'VALUE', 'more': []}]}]}

Если большая часть уровня содержит один или несколько элементов {'foo': None, ...}, обновите значение этих элементов. Иначе, просто добавьте {'foo': value, 'more': []} элемент.

...