Эффективный алгоритм сопоставления строк - PullRequest
4 голосов
/ 05 марта 2009

Я пытаюсь построить эффективный алгоритм сопоставления строк. Это будет выполняться в среде с большим объемом, поэтому производительность имеет решающее значение.

Вот мои требования:

  • Для данного доменного имени, например, www.example.com, определите, соответствует ли оно имени в списке записей.
  • Записи могут быть абсолютными совпадениями, т.е. www.example.com.
  • Записи могут включать символы подстановки, т.е. * .example.com.
  • Подстановочные знаки совпадают с наиболее определенного уровня и выше. Например, * .example.com будет соответствовать www.example.com, example.com и sub.www.example.com.
  • Записи с подстановочными знаками не встроены, т. Е. Sub. *. Example.com не будет записью.

Язык / среда: C # (.Net Framework 3.5)

Я рассмотрел разбиение записей (и поиск домена) на массивы, реверсирование порядка, а затем итерацию по массивам. Хотя точный, он чувствует себя медленно.

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

Мой вопрос: как эффективный способ найти, если строка в форме доменного имени соответствует какой-либо строке в списке строк, учитывая приведенное выше описание?

Ответы [ 14 ]

12 голосов
/ 05 марта 2009

Если вы хотите свернуть свои собственные, я бы сохранил записи в древовидной структуре. См. мой ответ на другой вопрос SO о средствах проверки орфографии, чтобы понять, что я имею в виду.

Вместо того, чтобы маркировать структуру с помощью "." символы, я бы просто рассматривал каждую запись как полную строку. Любая реализация с токенами все равно должна будет выполнять сопоставление строк с полным набором символов, так что вы можете сделать все это за один раз.

Единственные различия между этим и обычным деревом проверки орфографии:

  1. Соответствие должно быть сделано в обратном порядке
  2. Вы должны принять во внимание подстановочные знаки

Чтобы обратиться к пункту №2, вам просто нужно проверить наличие символа «*» в конце теста.

Быстрый пример:

Запись:

*.fark.com
www.cnn.com

Дерево:

m -> o -> c -> . -> k -> r -> a -> f -> . -> *
                \
                 -> n -> n -> c -> . -> w -> w -> w

Проверка www.blog.fark.com будет включать в себя трассировку по дереву до первого "*". Поскольку обход закончился на "*", есть совпадение.

Проверка того, что www.cern.com завершится с ошибкой на втором "n" из n, n, c, ...

Проверка dev.www.cnn.com также не будет выполнена, поскольку обход заканчивается на символе, отличном от "*".

5 голосов
/ 05 марта 2009

вам не нужно регулярное выражение ... просто переверните все строки, избавиться от '*' и поставить флажок, чтобы указать частичное совпадение пока эта точка не пройдет.

Каким-то образом три или суффикс-три выглядят наиболее подходящими.

Если список доменов известен во время компиляции, вы можете посмотреть на маркировка на '.' и с использованием нескольких машин, сгенерированных gperf.

Ссылки: Google для Trie http://marknelson.us/1996/08/01/suffix-trees/

5 голосов
/ 05 марта 2009

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

3 голосов
/ 05 марта 2009

Я бы использовал древовидную структуру для хранения правил, где каждый узел дерева содержит / содержит словарь.

Создайте дерево таким образом, чтобы «com», «net» и т. Д. Были записями верхнего уровня, «example» - на следующем уровне и т. Д. Вам понадобится специальный флаг, чтобы отметить, что узел является подстановочным знаком.

Чтобы выполнить поиск, разбейте строку по периодам и выполните итерации в обратном направлении, перемещаясь по дереву на основе ввода.

Это похоже на то, что, как вы сказали, вы рассматривали, но при условии, что правила не меняются при каждом запуске, использование кэшированного дерева на основе словаря будет быстрее, чем список массивов.

Кроме того, я хотел бы поспорить, что этот подход будет быстрее, чем RegEx.

2 голосов
/ 05 марта 2009

Похоже, у вас есть четко определенный набор правил относительно того, что вы считаете допустимым вводом - вы можете подумать об использовании для этого рукописного LL-парсера . Такие парсеры относительно легко написать и оптимизировать. Обычно у вас есть парсер, который выводит древовидную структуру, описывающую входные данные - я бы использовал это дерево в качестве входных данных для соответствующей процедуры, которая выполняет работу по сопоставлению дерева со списком записей, используя правила, описанные вами выше.

Вот статья о парсерах рекурсивного спуска .

1 голос
/ 05 марта 2009

Я собираюсь предложить альтернативу подходу древовидной структуры. Создайте сжатый индекс вашего списка доменов, используя преобразование Burrows-Wheeler. См. http://www.ddj.com/architect/184405504?pgno=1 для полного объяснения техники.

1 голос
/ 05 марта 2009

Я бы попробовал комбинацию попытки с совпадение с самым длинным префиксом (которое используется при маршрутизации для IP-сети). Направленные ациклические графы слов может быть более подходящим, чем попытки, если пробел является проблемой.

1 голос
/ 05 марта 2009

Предполагая, что правила такие же, как вы сказали: буквальные или начинаются с *.

Java:

public static boolean matches(String candidate, List<String> rules) {
    for(String rule : rules) {
        if (rule.startsWith("*")) {
            rule = rule.substring(2);
        }
        if (candidate.endsWith(rule)) {
            return true;
        }
    }
    return false;
}

Это масштабируется до количества правил, которые у вас есть.

EDIT:

Просто чтобы прояснить это.

Когда я говорю «сортировать правила», я действительно имею в виду создание дерева из символов правила.

Затем вы используете строку соответствия, чтобы попытаться пройтись по дереву (то есть, если у меня есть строка xyz, я начинаю с символа x и проверяю, имеет ли она ветвь y, а затем дочерний элемент z).

Для «подстановочных знаков» я бы использовал ту же концепцию, но заполнил ее «задом наперед», и прошел бы ее задним числом кандидата на матч.

Если у вас много (много) правил, я бы отсортировал правила.

В случае совпадений без подстановочных знаков вы выполняете итерацию для каждого символа, чтобы сузить возможные правила (т. Е. Если он начинается с "w", то вы работаете с правилами "w" и т.

Если это совпадение с подстановочным знаком, вы делаете то же самое, но работаете со списком «обратных правил» и просто сопоставляете конец строки с концом правила.

0 голосов
/ 05 марта 2009

Исследуйте алгоритмы KMP (Кнут-Моррис-Пратт) или BM (Бойер-Мур). Это позволяет вам искать строку быстрее, чем линейное время, за счет небольшой предварительной обработки. Как отмечают другие, исключение ведущей звездочки, безусловно, имеет решающее значение.

Один из источников информации для них:

KMP: http://www -igm.univ-mlv.fr / ~ lecroq / string / node8.html

BM: http://www -igm.univ-mlv.fr / ~ lecroq / string / node14.html

0 голосов
/ 05 марта 2009

Если это были просто совпадающие строки, то вам следует взглянуть на три структуры данных и алгоритмы. Более ранний ответ предполагает, что, если все ваши символы подстановки в начале являются одним символом подстановки, есть несколько конкретных алгоритмов, которые вы можете использовать. Однако требование обработки общих подстановочных знаков означает, что для быстрого выполнения вам потребуется сгенерировать конечный автомат.

Вот что библиотека регулярных выражений делает для вас: «прекомпилирует» регулярное выражение ==, генерируя конечный автомат; это позволяет быстро выполнять фактическое совпадение во время выполнения. Вы вряд ли получите значительно лучшую производительность, чем без особых усилий по оптимизации.

Если вы хотите создать свой собственный, я могу сказать, что написание собственного генератора конечных автоматов специально для нескольких подстановочных знаков должно быть познавательным. В этом случае вам необходимо ознакомиться с типами алгоритмов, которые они используют в библиотеках регулярных выражений ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...