Создание иерархического дерева из словаря содержимого страниц - PullRequest
1 голос
/ 27 ноября 2009

Следующие пары ключ: значение - это «страница» и «содержимое страницы».

{
  '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 ]

2 голосов
/ 27 ноября 2009

Вот простой подход - это 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 ing, вы можете 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 с подходящими методами.

1 голос
/ 28 ноября 2009

Вот иллюстрация к вашему вопросу. Про графики легче рассуждать, когда у вас есть картинка.

Сначала сократите данные:

#!/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.dot

0 голосов
/ 27 ноября 2009

РЕДАКТИРОВАТЬ Поскольку вопрос объяснен немного лучше, я думаю, что следующее может быть тем, что вам нужно, или, по крайней мере, может дать что-то начальное.

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])

Это должна быть одна строка, но я использовал \ как индикатор разрыва строки, чтобы его можно было прочитать без полос прокрутки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...