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)
что-то вроде этого будет работать.