«1. Обзор

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

Мы будем использовать простой цикл, Collection.removeAll() и регулярные выражения.

Наконец, мы сравним их производительность с помощью Java Microbenchmark Harness.

2. Загрузка стоп-слов

Сначала мы загрузим стоп-слова из текстового файла.

Здесь у нас есть файл english_stopwords.txt, который содержит список слов, которые мы считаем стоп-словами, таких как я, он, она и тот.

Мы загрузим стоп-слова в список строк, используя Files.readAllLines():

@BeforeClass
public static void loadStopwords() throws IOException {
    stopwords = Files.readAllLines(Paths.get("english_stopwords.txt"));
}

3. Удаление стоп-слов вручную

В нашем первом решении мы будем удалять стоп-слова вручную, перебирая каждое слово и проверка, является ли оно стоп-словом:

@Test
public void whenRemoveStopwordsManually_thenSuccess() {
    String original = "The quick brown fox jumps over the lazy dog"; 
    String target = "quick brown fox jumps lazy dog";
    String[] allWords = original.toLowerCase().split(" ");

    StringBuilder builder = new StringBuilder();
    for(String word : allWords) {
        if(!stopwords.contains(word)) {
            builder.append(word);
            builder.append(' ');
        }
    }
    
    String result = builder.toString().trim();
    assertEquals(result, target);
}

4. Использование Collection.removeAll()

Далее, вместо повторения каждого слова в нашей строке, мы можем использовать Collection.removeAll() для удаления всех стоп-слов в один раз:

@Test
public void whenRemoveStopwordsUsingRemoveAll_thenSuccess() {
    ArrayList<String> allWords = 
      Stream.of(original.toLowerCase().split(" "))
            .collect(Collectors.toCollection(ArrayList<String>::new));
    allWords.removeAll(stopwords);

    String result = allWords.stream().collect(Collectors.joining(" "));
    assertEquals(result, target);
}

В этом примере после разделения нашей строки на массив слов мы преобразуем ее в ArrayList, чтобы можно было применить метод removeAll().

5. Использование регулярных выражений

Наконец, мы можем создать регулярное выражение из нашего списка стоп-слов, а затем использовать его для замены стоп-слов в нашей строке:

@Test
public void whenRemoveStopwordsUsingRegex_thenSuccess() {
    String stopwordsRegex = stopwords.stream()
      .collect(Collectors.joining("|", "\\b(", ")\\b\\s?"));

    String result = original.toLowerCase().replaceAll(stopwordsRegex, "");
    assertEquals(result, target);
}

Результирующее стоп-слова будет иметь формат « \\\\b(он|она|the|…)\\\\b\\\\s?†. В этом регулярном выражении «\\b» относится к границе слова, чтобы избежать замены «he» в «heat», например, в то время как «\\s?» относится к нулю или одному пробелу, чтобы удалить дополнительный пробел после замены стоп-слова.

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

Теперь давайте посмотрим, какой метод имеет наилучшую производительность.

Во-первых, давайте настроим наш тест. Мы будем использовать довольно большой текстовый файл в качестве источника нашей строки с именем Shakespeare-hamlet.txt:

@Setup
public void setup() throws IOException {
    data = new String(Files.readAllBytes(Paths.get("shakespeare-hamlet.txt")));
    data = data.toLowerCase();
    stopwords = Files.readAllLines(Paths.get("english_stopwords.txt"));
    stopwordsRegex = stopwords.stream().collect(Collectors.joining("|", "\\b(", ")\\b\\s?"));
}

Затем у нас будут методы тестирования, начиная с removeManually():

@Benchmark
public String removeManually() {
    String[] allWords = data.split(" ");
    StringBuilder builder = new StringBuilder();
    for(String word : allWords) {
        if(!stopwords.contains(word)) {
            builder.append(word);
            builder.append(' ');
        }
    }
    return builder.toString().trim();
}

Далее , у нас есть бенчмарк removeAll():

@Benchmark
public String removeAll() {
    ArrayList<String> allWords = 
      Stream.of(data.split(" "))
            .collect(Collectors.toCollection(ArrayList<String>::new));
    allWords.removeAll(stopwords);
    return allWords.stream().collect(Collectors.joining(" "));
}

Наконец, мы добавим бенчмарк для replaceRegex():

@Benchmark
public String replaceRegex() {
    return data.replaceAll(stopwordsRegex, "");
}

И вот результат нашего бенчмарка:

Benchmark                           Mode  Cnt   Score    Error  Units
removeAll                           avgt   60   7.782 ±  0.076  ms/op
removeManually                      avgt   60   8.186 ±  0.348  ms/op
replaceRegex                        avgt   60  42.035 ±  1.098  ms/op

Кажется например, использование Collection.removeAll() имеет самое быстрое время выполнения, а использование регулярных выражений — самое медленное.

7. Заключение

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

Полный исходный код примеров доступен на GitHub.