Самые длинные совпадения префиксов для URL - PullRequest
10 голосов
/ 25 марта 2011

Мне нужна информация о любом стандартном пакете 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-адресов увеличивается.

Большое спасибо.

Ответы [ 4 ]

13 голосов
/ 30 марта 2011

Сравнение производительности

suffixtree с pytrie с trie с datrie с startswith -функциями

Настройка

записанное время является минимальным временем среди 3 повторений 1000 поисков.Время создания дерева включено и распределено по всем поискам.Поиск осуществляется по коллекциям имен хостов от 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

  • частый ключ

    До N = 100000datrie самый быстрый (за милЛев обращается ко времени, в котором доминирует время построения дерева).

    Наибольшее время уходит на нахождение наибольшего совпадения среди найденных совпадений.Таким образом, все функции работают так же, как и ожидалось.

frequent_key

startswith - время выполнения не зависит от типа ключа.

trie и pytrie ведут себя подобно друг другу.

Производительность без трехкратного построения

  • datrie - самое быстрое и достойное потребление памяти

  • startswith здесь еще более невыгоден, потому что другие подходы не штрафуются временем, которое требуется для построения дерева.

  • datrie, pytrie, trie - почти O (1) (постоянное время) для редкого / несуществующего ключа

rare_key_no_trie_build_time nonexistent_key_no_trie_build_time

frequent_key_no_trie_build_time

Подгонка (аппроксимация) полиномов известных функций для сравнения (тот же лог / логарифмическая шкала, что и на рисунках):

| 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               |
12 голосов
/ 25 марта 2011

Этот пример подходит для небольших списков 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 tree of example URLs

Принимая во внимание, что дерево больше похоже на: trie of example URLs

1 голос
/ 27 марта 2011

Если вы готовы обменять оперативную память на время, тогда 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()), чем @ решение Стивена Полера [которое основано на .startswith()] на Данные, которые я пробовал, и это может быть достаточно хорошо.

Чтобы установить SuffixTree, запустите:

pip install SuffixTree -f https://hkn.eecs.berkeley.edu/~dyoo/python/suffix_trees
1 голос
/ 25 марта 2011

Функция ниже вернет индекс самого длинного совпадения.Другая полезная информация также может быть легко извлечена.

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