Python: рекурсивно создавать словарь из путей

у меня есть несколько сотен тысяч URL-адресов конечных точек, для которых я хочу генерировать статистику. Например у меня есть:

/a/b/c
/a/b/d
/a/c/d
/b/c/d
/b/d/e
/a/b/c
/b/c/d

Я хочу создать словарь, который выглядит так

{
   {'a':
        {'b':
             {'c': 2 },
             {'d': 1 }
        },
        {'c':
             {'d': 1 }
        }
    },
    {'b':
        {'c':
             {'d': 2}
        },
        {'d':
             {'e': 1}
        }
    }
}

умные способы сделать это?

редактировать

Я должен упомянуть, что пути не всегда 3 части. Может быть ... /a/b/c/d/e/f/g/h... и т. д. и т. п.

4 ответов


если все пути выглядят как в вашем примере, это будет работать:

counts = {}
for p in paths:
   parts = p.split('/')
   branch = counts
   for part in parts[1:-1]:
      branch = branch.setdefault(part, {})
   branch[parts[-1]] = 1 + branch.get(parts[-1], 0)

это использует методы словаря, такие как setdefault() и get() чтобы избежать необходимости писать много if-операторов.

обратите внимание, что это не будет работать, если путь, который имеет подкаталоги, также может появиться самостоятельно. Тогда непонятно, является ли соответствующая часть counts должен содержать число или другой словарь. В этом случае, вероятно, было бы лучше сохраните как count, так и dict для каждого узла, используя кортеж или пользовательский класс.

основной алгоритм остается неизменным:

class Stats(object):
   def __init__(self):
      self.count = 0
      self.subdirs = {}

counts = Stats()
for p in paths:
   parts = p.split('/')
   branch = counts
   for part in parts[1:]:
      branch = branch.subdirs.setdefault(part, Stats())
   branch.count += 1

С некоторой симпатичной печатью вы получаете:

def printstats(stats, indent=''):
   print indent + str(stats.count) + ' times'
   for (d, s) in stats.subdirs.items():
       print indent + d + ':'
       printstats(s, indent + '  ')

>>> printstats(counts)
0 times
a:
  0 times
  c:
    0 times
    d:
      1 times
  b:
    0 times
    c:
      2 times
    d:
      1 times
...

EDIT:

Я изменил свой код, чтобы соответствовать вашему последнему комментарию выше (теперь нет сложной структуры данных).

def dictizeString(string, dictionary):
    while string.startswith('/'):
        string = string[1:]
    parts = string.split('/', 1)
    if len(parts) > 1:
        branch = dictionary.setdefault(parts[0], {})
        dictizeString(parts[1], branch)
    else:
        if dictionary.has_key(parts[0]):
             # If there's an addition error here, it's because invalid data was added
             dictionary[parts[0]] += 1
        else:
             dictionary[parts[0]] = 1

Он будет хранить список [frequency, dictionary] для каждого элемента.


вот моя попытка:

class Result(object):
    def __init__(self):
        self.count = 0
        self._sub_results = {}

    def __getitem__(self, key):
        if key not in self._sub_results:
            self._sub_results[key] = Result()
        return self._sub_results[key]

    def __str__(self):
        return "(%s, %s)" % (self.count, self._sub_results)

    def __repr__(self):
        return str(self)

def process_paths(paths):
    path_result = Result()
    for path in paths:
        components = path[1:].split("/")
        local_result = path_result
        for component in components:
            local_result = local_result[component]
        local_result.count += 1
    return path_result

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


старый результат, но все еще около вершины в google, поэтому я обновлю: вы можете использовать dpath-python для этого.

$ easy_install dpath
>>> result = {}
>>> for path in my_list_of_paths:
>>> ... dpath.util.set(result, path, SOME_VALUE)

... и это все. Я не понимаю математику, которую вы используете для предварительного вычисления этих значений на терминале (1, 2 и т. д.), Но вы можете предварительно вычислить ее и использовать словарь пути к значению вместо голого списка

>>> x = {'path/name': 0, 'other/path/name': 1}
>>> for (path, value) in x.iteritems():
>>> ... dpath.util.set(result, path, value)

что-то вроде этого будет работать.