Самые длинные префиксные соответствия для URL-адресов
Мне нужна информация о любом стандартном пакете python, который можно использовать для "длинного совпадения префикса" по URL-адресам. Я прошел через два стандартных пакета http://packages.python.org/PyTrie/#pytrie.StringTrie и 'http://pypi.python.org/pypi/trie/0.1.1', но они не похоже, полезен для самой длинной задачи сопоставления префикса по URL-адресам.
Если у моего набора есть эти URL-адреса: 1- > http://www.google.com/mail, 2- > http://www.google.com/document, 3- > http://www. facebook.com и т.д.
Теперь, если я ищу "http://www.google.com/doc", он должен вернуть 2, и поиск "http://www.face" должен вернуть 3.
Я хотел подтвердить, есть ли какой-либо стандартный пакет python, который может помочь мне в этом, или я должен реализовать соответствие Trie для префикса.
Я не ищу решение для регулярного выражения, так как оно не масштабируется по мере увеличения количества URL.
Большое спасибо.
Ответы
Ответ 1
Этот пример хорош для небольших списков url, но не очень хорошо масштабируется.
def longest_prefix_match(search, urllist):
matches = [url for url in urllist if url.startswith(search)]
if matches:
return max(matches, key=len)
else:
raise Exception("Not found")
Реализация с использованием модуля trie.
import trie
def longest_prefix_match(prefix_trie, search):
# There may well be a more elegant way to do this without using
# "hidden" method _getnode.
try:
return list(node.value for node in prefix_trie._getnode(search).walk())
except KeyError:
return list()
url_list = [
'http://www.google.com/mail',
'http://www.google.com/document',
'http://www.facebook.com',
]
url_trie = trie.Trie()
for url in url_list:
url_trie[url] = url
searches = ("http", "http://www.go", "http://www.fa", "http://fail")
for search in searches:
print "'%s' ->" % search, longest_prefix_match(url_trie, search)
Результат:
'http' -> ['http://www.facebook.com', 'http://www.google.com/document', 'http://www.google.com/mail']
'http://www.go' -> ['http://www.google.com/document', 'http://www.google.com/mail']
'http://www.fa' -> ['http://www.facebook.com']
'http://fail' -> []
или используя PyTrie, который дает тот же результат, но списки упорядочены по-разному.
from pytrie import StringTrie
url_list = [
'http://www.google.com/mail',
'http://www.google.com/document',
'http://www.facebook.com',
]
url_trie = StringTrie()
for url in url_list:
url_trie[url] = url
searches = ("http", "http://www.go", "http://www.fa", "http://fail")
for search in searches:
print "'%s' ->" % search, url_trie.values(prefix=search)
Я начинаю думать, что дерево radix/patricia tree будет лучше с точки зрения использования памяти. Это будет выглядеть дерево оснований:
![Radix tree of example URLs]()
В то время как trie больше напоминает:
![trie of example URLs]()
Ответ 2
Сравнение производительности
suffixtree
vs. pytrie
vs. trie
vs. datrie
vs. startswith
-функции
Настройка
Записанное время - это минимальное время из 3 повторений 1000 запросов. Время построения trie включено и распространяется среди всех запросов. Поиск выполняется по коллекциям имен хостов от 1 до 1000000 элементов.
Три типа строки поиска:
-
non_existent_key
- нет соответствия для строки
-
rare_key
- около 20 в миллионах
-
frequent_key
- количество вхождений сопоставимо с размером коллекции
Результаты
Максимальное потребление памяти для миллионов URL:
| function | memory, | ratio |
| | GiB | |
|-------------+---------+-------|
| suffix_tree | 0.853 | 1.0 |
| pytrie | 3.383 | 4.0 |
| trie | 3.803 | 4.5 |
| datrie | 0.194 | 0.2 |
| startswith | 0.069 | 0.1 |
#+TBLFM: $3=$2/@3$2;%.1f
Чтобы воспроизвести результаты, запустить тестовый код trie.
-
Случай с редким ключом/несуществующим ключом
если количество URL-адресов меньше 10000, тогда datrie является самым быстрым, для
N > 10000 - suffixtree
быстрее, startwith
значительно медленнее в среднем.
![rare_key]()
-
осей:
- вертикальная шкала (время) составляет ~ 1 секунду (2 ** 20 микросекунд)
Горизонтальная ось
- показывает общее количество URL-адресов в каждом случае: N = 1, 10, 100, 1000, 10000, 100000 и 1000000 (млн.).
![nonexistent_key]()
-
frequent_key
Upto N = 100000 datrie
является самым быстрым (за миллион URL-адресов время
где доминирует время построения trie).
Самое время занимает самое длинное совпадение найденных матчей. Таким образом, все функции ведут себя как ожидалось.
![frequent_key]()
startswith
- производительность времени не зависит от типа ключа.
trie
и pytrie
ведут себя аналогично друг другу.
Производительность без времени построения trie
-
datrie
- самое быстрое и достойное потребление памяти
-
startswith
еще более невыгодно, потому что другие подходы не наказываются временем, затрачиваемым на создание trie.
-
datrie
, pytrie
, trie
- почти O (1) (постоянное время) для редкого/несуществующего ключа
![rare_key_no_trie_build_time]()
![nonexistent_key_no_trie_build_time]()
![frequent_key_no_trie_build_time]()
Фиксирование (аппроксимация) полиномов известных функций для сравнения (та же шкала log/log, что и на рисунках):
| Fitting polynom | Function |
|------------------------------+-------------------|
| 0.15 log2(N) + 1.583 | log2(N) |
| 0.30 log2(N) + 3.167 | log2(N)*log2(N) |
| 0.50 log2(N) + 1.111e-15 | sqrt(N) |
| 0.80 log2(N) + 7.943e-16 | N**0.8 |
| 1.00 log2(N) + 2.223e-15 | N |
| 2.00 log2(N) + 4.446e-15 | N*N |
Ответ 3
Функция ниже вернет индекс самого длинного совпадения. Другая полезная информация также может быть легко извлечена.
from os.path import commonprefix as oscp
def longest_prefix(s, slist):
pfx_idx = ((oscp([s, url]), i) for i, url in enumerate(slist))
len_pfx_idx = map(lambda t: (len(t[0]), t[0], t[1]), pfx_idx)
length, pfx, idx = max(len_pfx_idx)
return idx
slist = [
'http://www.google.com/mail',
'http://www.google.com/document',
'http://www.facebook.com',
]
print(longest_prefix('http://www.google.com/doc', slist))
print(longest_prefix('http://www.face', slist))
Ответ 4
Если вы готовы торговать оперативной памятью для производительности времени, тогда SuffixTree
может оказаться полезным. Он обладает хорошими алгоритмическими свойствами, такими как позволяет решать самую длинную общую задачу подстроки в линейном времени.
Если вы всегда ищете префикс, а не произвольную подстроку, вы можете добавить уникальный префикс при заполнении SubstringDict()
:
from SuffixTree import SubstringDict
substr_dict = SubstringDict()
for url in URLS: # urls must be ascii (valid urls are)
assert '\n' not in url
substr_dict['\n'+url] = url #NOTE: assume that '\n' can't be in a url
def longest_match(url_prefix, _substr_dict=substr_dict):
matches = _substr_dict['\n'+url_prefix]
return max(matches, key=len) if matches else ''
Такое использование SuffixTree
кажется субоптимальным, но оно в 20-150 раз быстрее (без SubstringDict()
времени построения), чем решение @StephenPaulger [которое основано на .startswith()
] по данным, которые я пробовал, и это может быть достаточно хорошим.
Чтобы установить SuffixTree
, запустите:
pip install SuffixTree -f https://hkn.eecs.berkeley.edu/~dyoo/python/suffix_trees