Создание иерархического дерева из словаря содержимого страниц
Следующие пары ключ: значение - это "страница" и "содержимое страницы".
{
'section-a.html':{'contents':'section-b.html section-c.html section-d.html'},
'section-b.html':{'contents':'section-d.html section-e.html'},
'section-c.html':{'contents':'product-a.html product-b.html product-c.html product-d.html'},
'section-d.html':{'contents':'product-a.html product-c.html'},
'section-e.html':{'contents':'product-b.html product-d.html'},
'product-a.html':{'contents':''},
'product-b.html':{'contents':''},
'product-c.html':{'contents':''},
'product-d.html':{'contents':''}
}
Для любого данного "предмета", как я могу найти путь (и) к указанному предмету? С моим очень ограниченным знанием структур данных в большинстве случаев я предполагаю, что это будет иерархическое дерево. Пожалуйста, поправьте меня, если я ошибаюсь!
ОБНОВЛЕНИЕ: мои извинения, я должен был быть более ясным о данных и моем ожидаемом результате.
Предполагая, что "page-a" является индексом, каждая "страница" буквально представляет собой страницу, отображаемую на веб-сайте, где каждый "элемент" представляет собой страницу продукта, которая будет отображаться в Amazon, Newegg и т. Д.
Таким образом, мой ожидаемый результат для 'item-d' будет путём (или путями) к этому предмету. Например (разделитель является произвольным, для иллюстрации здесь): item-d имеет следующие пути:
page-a > page-b > page-e > item-d
page-a > page-c > item-d
ОБНОВЛЕНИЕ 2: Обновил мой оригинал dict
предоставлять более точные и реальные данные. ".html" добавлен для уточнения.
3 ответа
Вот простой подход - это O(N в квадрате), так что, не все, что хорошо масштабируется, но хорошо послужит вам для разумного размера книги (если у вас есть, скажем, миллионы страниц, вам нужно подумать о очень другой и менее простой подход;-).
Во-первых, создайте более удобный для использования диктет, сопоставляя страницу с набором содержимого: например, если исходный диктат d
сделай еще один дикт mud
как:
mud = dict((p, set(d[p]['contents'].split())) for p in d)
Затем сделайте dict, отображающий каждую страницу на ее родительские страницы:
parent = dict((p, [k for k in mud if p in mud[k]]) for p in mud)
Здесь я использую списки родительских страниц (наборы тоже подойдут), но это нормально для страниц с 0 или 1 родителем, как и в вашем примере - вы просто будете использовать пустой список для обозначения "нет родителя" ", иначе список с родителем в качестве единственного элемента. Это должен быть ациклический ориентированный граф (если у вас есть сомнения, вы можете проверить, конечно, но я пропускаю эту проверку).
Теперь, для данной страницы, для поиска путей от своего родителя (ов) к родителю без родителей ("корневой странице") просто требуется "пройтись" parent
ДИКТ. Например, в родительском случае 0/1:
path = [page]
while parent[path[-1]]:
path.append(parent[path[-1]][0])
Если вы сможете лучше уточнить свои спецификации (диапазоны количества страниц на книгу, количество родителей на страницу и т. Д.), Этот код, без сомнения, можно уточнить, но для начала я надеюсь, что он может помочь.
Изменить: как ОП пояснил, что случаи с> 1 родителем (и, таким образом, несколько путей) действительно представляют интерес, позвольте мне показать, как справиться с этим:
partial_paths = [ [page] ]
while partial_paths:
path = partial_paths.pop()
if parent[path[-1]]:
# add as many partial paths as open from here
for p in parent[path[-1]]:
partial_paths.append(path + [p])
else:
# we've reached a root (parentless node)
print(path)
Конечно, вместо print
Я могу yield
каждый путь, когда он достигает корня (превращая функцию, тело которой в генератор), или иным образом обрабатывает его так, как вам нужно.
Снова отредактируйте: комментатор беспокоится о циклах на графике. Если это беспокойство оправдано, нетрудно отслеживать узлы, уже замеченные на пути, а также обнаруживать и предупреждать о любых циклах. Быстрее всего сохранить набор рядом с каждым списком, представляющим частичный путь (нам нужен список для упорядочения, но проверка на членство - это O(1) в наборах против O(N) в списках):
partial_paths = [ ([page], set([page])) ]
while partial_paths:
path, pset = partial_paths.pop()
if parent[path[-1]]:
# add as many partial paths as open from here
for p in parent[path[-1]]:
if p in pset:
print('Cycle: %s (%s)' % (path, p))
continue
partial_paths.append((path + [p], pset.union([p])))
else:
# we've reached a root (parentless node)
print('Path: %s' % (path,))
Для ясности, вероятно, стоит упаковать список и набор, представляющий частичный путь, в небольшой служебный класс Path с подходящими методами.
Вот иллюстрация к вашему вопросу. Про графики легче рассуждать, когда у вас есть картинка.
Сначала сокращаем данные:
#!/usr/bin/perl -pe
s/section-([a-e])\.html/uc$1/eg; s/product-([a-e])\.html/$1/g
Результат:
# graph as adj list
DATA = {
'A':{'contents':'B C D'},
'B':{'contents':'D E'},
'C':{'contents':'a b c d'},
'D':{'contents':'a c'},
'E':{'contents':'b d'},
'a':{'contents':''},
'b':{'contents':''},
'c':{'contents':''},
'd':{'contents':''}
}
Преобразовать в формат графика:
with open('data.dot', 'w') as f:
print >> f, 'digraph {'
for node, v in data.iteritems():
for child in v['contents'].split():
print >> f, '%s -> %s;' % (node, child),
if v['contents']: # don't print empty lines
print >> f
print >> f, '}'
Результат:
digraph {
A -> C; A -> B; A -> D;
C -> a; C -> b; C -> c; C -> d;
B -> E; B -> D;
E -> b; E -> d;
D -> a; D -> c;
}
Постройте график:
$ dot -Tpng -O data.dot
РЕДАКТИРОВАТЬ С учетом того, что вопрос объяснен немного лучше, я думаю, что следующее может быть тем, что вам нужно, или, по крайней мере, может послужить отправной точкой.
data = {
'section-a.html':{'contents':'section-b.html section-c.html section-d.html'},
'section-b.html':{'contents':'section-d.html section-e.html'},
'section-c.html':{'contents':\
'product-a.html product-b.html product-c.html product-d.html'},
'section-d.html':{'contents':'product-a.html product-c.html'},
'section-e.html':{'contents':'product-b.html product-d.html'},
'product-a.html':{'contents':''},
'product-b.html':{'contents':''},
'product-c.html':{'contents':''},
'product-d.html':{'contents':''}
}
def findSingleItemInData(item):
return map( lambda x: (item, x), \
[key for key in data if data[key]['contents'].find(item) <> -1])
def trace(text):
searchResult = findSingleItemInData(text)
if not searchResult:
return text
retval = []
for item in searchResult:
retval.append([text, trace(item[-1])])
return retval
print trace('product-d.html')
OLD
Я действительно не знаю, что вы ожидаете увидеть, но, возможно, что-то подобное сработает.
data = {
'page-a':{'contents':'page-b page-c'},
'page-b':{'contents':'page-d page-e'},
'page-c':{'contents':'item-a item-b item-c item-d'},
'page-d':{'contents':'item-a item-c'},
'page-e':{'contents':'item-b item-d'}
}
itemToFind = 'item-c'
for key in data:
for index, item in enumerate(data[key]['contents'].split()):
if item == itemToFind:
print key, 'at position', index
Было бы проще, и я считаю более правильным, если бы вы использовали немного другую структуру данных:
data = {
'page-a':{'contents':['page-b', 'page-c']},
'page-b':{'contents':['page-d', 'page-e']},
'page-c':{'contents':['item-a', 'item-b item-c item-d']},
'page-d':{'contents':['item-a', 'item-c']},
'page-e':{'contents':['item-b', 'item-d']}
}
Тогда вам не нужно будет разделяться.
Учитывая этот последний случай, он даже может быть выражен немного короче:
for key in data:
print [ (key, index, value) for index,value in \
enumerate(data[key]['contents']) if value == 'item-c' ]
И еще короче, с удаленными пустыми списками:
print filter(None, [[ (key, index, value) for index,value in \
enumerate(data[key]['contents']) if value == 'item-c' ] for key in data])
Это должна быть одна строка, но я использовал \ как индикатор разрыва строки, чтобы его можно было читать без полос прокрутки.