Самые длинные префиксные соответствия для 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_timenonexistent_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