У меня есть словарь Python с довольно сложной структурой — несколько слоев вложенных значений, некоторые из которых являются словарями, а некоторые — списками. Я хочу представить изменения данных в компактном виде, который можно легко применить.
Для значений только из словаря это кажется не слишком сложным — вы можете просто создать словарь, отражающий структуру основных данных, но включающий только измененные ключи их родителей, и вызвать слегка измененный .update(), который обнаруживает значение захоронения. если вам нужно полностью удалить ключ.
Но со списками все становится намного сложнее. Похоже, мне нужно придумать какую-то собственную схему адресации, которая должна учитывать множество случаев — вы не можете просто наивно использовать индексы списка в качестве ключей, потому что вам нужно поддерживать, например. удаление элемента 5 одновременно с вставкой между элементами 2 и 3.
Кроме того, если списки не ограничены тем, что они являются листьями, сложно указывать изменения элементов, содержащихся в списке, одновременно изменяя элементы этого списка.
Есть ли библиотека Python, которая стандартизирует что-то подобное? Или стандартный алгоритм/подход, который относительно разумен для реализации?
для справки, вот функция, которая реализует то, что я ищу для данных только для dict:
def update(d, u):
for k, v in u.items():
if v == 'del':
del d[k]
elif isinstance(v, collections.abc.Mapping):
d[k] = update(d.get(k, {}), v)
else:
d[k] = v
return d
>>> d = {1: 2, 3: {4: 5, 6: 7}}
>>> delta = {3: {4: 'del', 6: 8}, 9: 10}
>>> update(d, delta)
{1: 2, 3: {6: 8}, 9: 10}
Я не знаю, что вы имеете в виду. Если я попытаюсь выполнить вставку в старый индекс после того, как более ранний элемент был удален, я сделаю вставку не в том месте.
Я имел в виду, что с точки зрения пользователя индексы имеют смысл. Конечно, с точки зрения реализации вы должны быть очень осторожны.
С точки зрения пользователя, я не думаю, что списковые индексы представляют собой такую большую проблему, как вы говорите. Индексы изменятся только после выполнения всех манипуляций. Используйте старые индексы при работе со списком.
С точки зрения реализации нам нужно быть очень осторожными при манипулировании индексами списка. Что мы можем сделать, так это поддерживать «дельту индекса i
» во время итерации, и вместо изменения l[k]
мы изменяем l[k+i]
.
Здесь я буду использовать словарь для дельты со списком индексов в качестве ключа и этими тремя возможными значениями:
'del'
удалить элемент по этому индексу;('insert', v)
чтобы вставить значение v непосредственно перед этим индексом; иv
, чтобы изменить значение на v
в этом индексе.Обратите внимание, что 'del'
и v
являются взаимоисключающими, но 'insert'
может быть кумулятивным: можно вставить более одного элемента в один и тот же индекс, а также можно вставить элементы непосредственно перед индексом и удалить или изменить элемент в этом индексе. Итак, мы хотим, чтобы наша дельта dict могла сопоставлять ключ с более чем одним обновлением; т. е. для сопоставления ключа со списком.
from operator import itemgetter
def update(d, u):
if isinstance(d, dict):
return update_dict(d, u)
elif isinstance(d, list):
return update_list(d, u)
def update_dict(d, u):
for k, v in u.items():
if v == 'del':
del d[k]
elif isinstance(v, dict):
d[k] = update(d.get(k, {}), v)
else:
d[k] = v
return d
def update_list(d, u):
i = 0
for k, v in sorted(u.items(), key=itemgetter(0)):
if isinstance(v, list):
for x in v:
i = update_list_once(d, i, k, x)
else:
i = update_list_once(d, i, k, v)
return d
def update_list_once(d, i, k, v):
if v == 'del':
del d[k+i]
i -= 1
elif isinstance(v, tuple) and len(v) == 2 and v[0] == 'insert':
d.insert(k+i, v[1])
i += 1
else:
if isinstance(v, dict):
d[k + i] = update(d[k+i], v)
else:
d[k+i] = v
return i
Тестирование:
d = {1: 2, 3: {4: [0, 1, 2, 3, 4, 5], 6: 7}}
delta = {3: {4: {0: 'fizzbuzz', 3: 'fizz', 4: [('insert', 3.5), 4.001], 5: 'buzz'}, 6: 8}, 9: 10}
d = update(d, delta)
print(d)
# {1: 2, 3: {4: ['fizzbuzz', 1, 2, 'fizz', 3.5, 4.001, 'buzz'], 6: 8}, 9: 10}
Это действительно солидное начало, и сначала я подумал, что вы прибили его, но мои опасения были в некоторой степени оправданы: поскольку вы используете старые индексы списка, нет возможности изменить элемент в позиции n, а также вставить элемент перед ним. . когда вы выполняете вставку, элемент в этой позиции становится невидимым, потому что вы не можете снова использовать этот ключ. (например, пытаясь создать список ['шипение', 1, 2, 'шипение', 3.5, 7, 'жужжание'], вы ничего не можете поместить в дельту, чтобы указать, что 7.)
@Personman Ооооооооо, ты прав
@Personman Нам нужен «мультидикт». То есть, dict, который может иметь более одного значения для каждого ключа. Мы бы хотели что-то вроде {4: [('insert', 3.5), 7]}
Да, я думаю, это также решает проблему невозможности сделать несколько вставок в одном месте. Я, вероятно, закончу что-то вроде этого, но я надеялся, что есть либо более простой способ, либо хороший существующий инструмент, чтобы сделать это за меня: p
@Personman я редактировал. Теперь это должно работать.
Это было бы хорошим вариантом использования more_itertools.always_iterable.
@Personman Обратите внимание, что большинство функций в библиотеке python следуют этому соглашению: либо функция изменяет свой ввод на месте и возвращает None
, либо не изменяет свой ввод и возвращает что-то. Например, list.sort
изменяет свой ввод и возвращает None
, а sorted
не изменяет свой ввод, а возвращает новый список. Функция update
выше представляет собой смесь двух, что не очень хорошо.
Я не думаю, что вставка - это проблема с индексами списка. Выполните все манипуляции со списком, используя старые индексы. Только после того, как все манипуляции проделаны, индексы меняются.