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