Java: сопоставление фраз в строке

У меня есть список фраз (фраза может состоять из одного или нескольких слов) в базе данных и входная строка. Мне нужно выяснить, какие из этих фраз появляются во входной строке.

Есть ли эффективный способ выполнить такое сопоставление в Java?


person medvaržtis    schedule 17.05.2011    source источник
comment
У вас есть пример фразы или входной строки? Многие решения можно рассматривать с использованием java или SQL.   -  person VirtualTroll    schedule 17.05.2011
comment
Примерами фраз могут быть частный капитал и программное обеспечение. И скажем, входная строка: американская компания прямых инвестиций, как полагают, готовит предложение на сумму 425-450 пенсов за акцию для британской группы разработчиков программного обеспечения, которая на этой неделе сообщила, что получила запрос, касающийся возможного поглощения. Для обеих фраз мне нужно получить положительный ответ об их наличии в строке.   -  person medvaržtis    schedule 18.05.2011
comment
@ medvaržtis: я, вероятно, рассмотрю структуру данных, такую ​​​​как aho-corasick или дерево суффиксов. Нет простого решения ни в java, ни в sql   -  person VirtualTroll    schedule 18.05.2011


Ответы (4)


Быстрый взлом будет:

  1. Создайте регулярное выражение на основе объединенных фраз
  2. Создайте набор, в котором перечислены фразы, которые еще не совпали
  3. Повторяйте find до тех пор, пока не будут найдены все фразы или не будет достигнут конец ввода, удаляя совпадения из набора оставшихся фраз для поиска.

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

Пример кода (протестирован, но не оптимизирован и не профилирован для производительности):

public static boolean hasAllPhrasesInInput(List<String> phrases, String input) {
    Set<String> phrasesToFind = new HashSet<String>();
    StringBuilder sb = new StringBuilder();
    for (String phrase : phrases) {
        if (sb.length() > 0) {
            sb.append('|');
        }
        sb.append(Pattern.quote(phrase));
        phrasesToFind.add(phrase.toLowerCase());
    }
    Pattern pattern = Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher(input);
    while (matcher.find()) {
        phrasesToFind.remove(matcher.group().toLowerCase());
        if (phrasesToFind.isEmpty()) {
            return true;
        }
    }
    return false;
}

Некоторые предостережения:

  • Приведенный выше код будет сопоставлять фразы как подстроки слов. Если должны совпадать только полные слова, вам нужно будет добавить границы слов ("\b") к сгенерированным регулярным выражениям.
  • Код необходимо изменить, если некоторые фразы могут быть подстроками других фраз.
  • Если вам нужно сопоставить текст, отличный от ASCII, вы должны добавить параметр регулярного выражения Pattern.UNICODE_CASE и вызвать toLowerCase(Locale) вместо toLowerCase(), используя подходящий Locale.
person markusk    schedule 17.05.2011
comment
+1 за беспокойство, которое вы взяли на себя, чтобы написать что-то длинное и информативное. Спасибо @markusk. - person Sid; 18.05.2011
comment
Хотя это не та проблема, которую мне нужно решить, я понял идею и реализовал ее. Спасибо @markusk! - person medvaržtis; 19.05.2011

Вот решение с использованием java. Поскольку вы ничего не указали о строках, которые вы используете, я рассматриваю общий пример

Pattern p = Pattern.compile("cat");
        // Create a matcher with an input string
Matcher m = p.matcher("one cat," +" two cats in the yard");
boolean b = m.matches();  // Should return true

надеюсь, это поможет

Ссылка: http://java.sun.com/developer/technicalArticles/releases/1.4regex/

person Shaunak    schedule 17.05.2011
comment
Ну, я думаю, что это должно быть m.find() вместо m.matches. Однако я не считаю это, как и String.contains(), подходящим решением. В моей базе около 1000 фраз. Итак, для каждой отдельной фразы мне пришлось бы снова вызывать эти методы. Я не думаю, что эффективно вызывать String.contains() или Matcher.find() 1000 раз. - person medvaržtis; 18.05.2011
comment
Я не думаю, что у вас возникнут проблемы с производительностью при использовании String.contains(). Извлечение 1000 совпадающих слов из базы данных, скорее всего, будет медленнее, чем просмотр их в цикле и сравнение их со строкой. Я попробовал вашу фразу с 1000 поисковых слов и string.contains, и это заняло 1 мс. - person ScArcher2; 18.05.2011

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

person Olaf    schedule 17.05.2011
comment
Ой! Я только что понял, что @Amine упомянул этот алгоритм в комментариях. - person Olaf; 18.05.2011

sql = "SELECT phrase " + 
  " FROM phrases " + 
  " WHERE phrase LIKE $1";     
PreparedStatement pstmt =  conn.prepareStatement (sql);
// probably repeated, if more than one input:
pstmt.setString (1, "%" + input + "%");
ResultSet rs = pstmt.executeQuery ();

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

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

person user unknown    schedule 17.05.2011