Я просмотрел несколько ответов на StackOverflow, но ни один из них не соответствует моему варианту использования. Я преобразовал ближайший ответ, в котором использовались кортежи, в код, использующий словари, и заставил его работать:
def get_matching_parent_entry_recursive(entries_to_traverse, parent_id):
for entry in entries_to_traverse:
if entry["id"] == parent_id:
return entry
if len(entry["items"]) > 0:
matching_entry = get_matching_parent_entry_recursive(entry["items"], parent_id)
if matching_entry:
return matching_entry
return None
def get_content_tree(categories):
simplified_entries = []
unmatched_entries = categories
while len(unmatched_entries) > 0:
i = 0
while i < len(unmatched_entries):
if unmatched_entries[i]["parent"] == 0: # we are dealing with a top-level entry
simplified_entries.append(
{
"id": str(unmatched_entries[i]["id"]),
"label": unmatched_entries[i]["label"],
"items": []
}
)
del unmatched_entries[i]
else:
# try to find parent entry in the final simplified tree
parent_entry = get_matching_parent_entry_recursive(
simplified_entries,
str(unmatched_entries[i]["parent"])
)
if parent_entry:
parent_entry["items"].append({
"id": str(unmatched_entries[i]["id"]),
"label": unmatched_entries[i]["label"],
"items": []
})
del unmatched_entries[i]
else:
i += 1
return simplified_entries
categories = [
{"id": 1, "label": 'Lamps', "parent": 0},
{"id": 2, "label": 'Lamp 1', "parent": 1},
{"id": 3, "label": 'Lamp 2', "parent": 1},
{"id": 4, "label": 'Shirts', "parent": 0},
{"id": 5, "label": 'Formal shirts', "parent": 4},
{"id": 7, "label": 'T-Shirts', "parent": 9},
{"id": 9, "label": 'Informal shirts', "parent": 4},
]
print(get_content_tree(categories))
Однако я вижу, что этот алгоритм должен пройти через каждый отдельный узел, чтобы найти соответствующий родитель, и это плохо для производительности. Есть ли более быстрый способ получить тот же результат?
Обновлено: Повторить ссылку
Обновлено еще раз: Идентификаторы уникальны и могут хэшироваться, но представлены не так, чтобы гарантировать, что родитель будет создан до того, как дочерний элемент сошлется на него. т.е. все футболки можно переместить во вновь созданную категорию, которая будет иметь идентификатор 9, но идентификатор футболки останется равным 7.
Предполагая, что идентификаторы уникальны (и хэшируются), вы можете хранить ссылку на каждый словарь под отдельными ключами:
categories = [
{"id": 1, "label": "Lamps", "parent": 0},
{"id": 2, "label": "Lamp 1", "parent": 1},
{"id": 3, "label": "Lamp 2", "parent": 1},
{"id": 4, "label": "Shirts", "parent": 0},
{"id": 5, "label": "Formal shirts", "parent": 4},
{"id": 6, "label": "Informal shirts", "parent": 4},
{"id": 7, "label": "T-Shirts", "parent": 6},
]
root = {0: {"items": []}}
for c in categories:
raw = {"id": c["id"], "items": [], "label": c["label"]}
root[c["id"]] = raw
root[c["parent"]]["items"].append(raw)
print(root[0]["items"])
Это не сработает, если категории не топологически отсортированный. В таком случае мы могли бы выполнить эту сортировку (лучшее решение... в теории) или сделать небольшое улучшение, которое в реальном случае должно быть быстрее и намного элегантнее:
from collections import deque
categories = deque(
[
{"id": 1, "label": "Lamps", "parent": 0},
{"id": 2, "label": "Lamp 1", "parent": 1},
{"id": 3, "label": "Lamp 2", "parent": 1},
{"id": 4, "label": "Shirts", "parent": 0},
{"id": 5, "label": "Formal shirts", "parent": 4},
{"id": 7, "label": "T-Shirts", "parent": 9},
{"id": 9, "label": "Informal shirts", "parent": 4},
]
)
root = {0: {"items": []}}
while categories:
c = categories.popleft()
raw = {"id": c["id"], "items": [], "label": c["label"]}
try:
# order is important here, as we don't want to create reference in root dictionary
root[c["parent"]]["items"].append(raw)
root[c["id"]] = raw
except KeyError:
# We didn't process parent category yet, lets back to that later
categories.append(c)
print(root[0]["items"])
Я изменил категории в своем коде выше, чтобы отразить эту ситуацию. Код в примере все еще работает
@tonysepia Спасибо! Я обновил ответ, чтобы отразить ваш случай, надеюсь, теперь он работает для вас!
большое спасибо за ваше время! Ссылка действительно интересная, и решение работает и очень эффективно!
Код прекрасен! У меня с ним только одна проблема: если я поменяю местами категории с id 6 и 7, то получим ключевую ошибку. Это могло произойти (и происходит) в моем случае, если кто-то заново создал категорию. идентификаторы по-прежнему уникальны и могут хешироваться, но не представлены для того, чтобы гарантировать, что родитель будет создан до того, как дочерний элемент сошлется на него. т.е. все футболки были перемещены в новую категорию с ID 9