Java:计算字符串中出现的单词数

源节点: 1719850

介绍

计算字符串中单词出现的次数是一项相当容易的任务,但有几种方法可以做到这一点。 您还必须考虑该方法的效率,因为当您不想执行体力劳动时(即搜索空间很大时),您通常会希望使用自动化工具。

在本指南中,您将学习如何在 Java 中计算字符串中单词出现的次数:

String searchText = "Your body may be chrome, but the heart never changes. It wants what it wants.";
String targetWord = "wants";

我们将搜索出现的次数 targetWord使用 String.split(), Collections.frequency() 和正则表达式。

计算字符串中的单词出现次数 字符串.split()

计算字符串中目标单词出现的最简单方法是在每个单词上拆分字符串,并遍历数组,递增 a wordCount 在每场比赛中。 请注意,当单词周围有任何标点符号时,例如 wants. 在句子的结尾——简单的词级分割将正确对待 wantswants. 作为单独的词!

要解决此问题,您可以轻松地从句子中删除所有标点符号 before 拆分它:

String[] words = searchText.replaceAll("p{Punct}", "").split(" ");

int wordCount = 0;
for (int i=0; i < words.length; i++)
    if (words[i].equals(targetWord))
        wordCount++;
System.out.println(wordCount);

for 循环,我们简单地遍历数组,检查每个索引处的元素是否等于 targetWord. 如果是,我们增加 wordCount,在执行结束时打印:

2

计算字符串中的单词出现次数 集合.频率()

Collections.frequency() 方法提供了一个更干净、更高级别的实现,它抽象出一个简单的 for 循环,并检查两个身份(无论是一个对象 is 另一个对象)和相等性(一个对象是否等于另一个对象,取决于该对象的定性特征)。

frequency() 方法接受要搜索的列表和目标对象,并且也适用于所有其他对象,其中行为取决于对象本身的实现方式 equals(). 在字符串的情况下, equals() 检查 字符串的内容:


searchText = searchText.replaceAll("p{Punct}", "");

int wordCount = Collections.frequency(Arrays.asList(searchText.split(" ")), targetWord);
System.out.println(wordCount);

在这里,我们已经转换了从 split() 进入Java ArrayList, 使用助手 asList() 的方法 Arrays 班级。 归约操作 frequency() 返回一个整数,表示频率 targetWord 在列表中,并导致:

2

字符串中的单词出现 匹配器(正则表达式 - RegEx)

最后,您可以使用正则表达式来搜索模式,并计算匹配模式的数量。 正则表达式就是为此而生的,因此它非常适合这项任务。 在 Java 中, Pattern 类用于表示和编译正则表达式,而 Matcher 类用于查找和匹配模式。

使用 RegEx,我们可以将标点符号不变性编码到表达式本身中,因此无需从外部格式化字符串或删除标点符号,这对于在内存中存储另一个更改版本可能很昂贵的大型文本来说是可取的:

Pattern pattern = Pattern.compile("b%s(?!w)".format(targetWord));

Pattern pattern = Pattern.compile("bwants(?!w)");
Matcher matcher = pattern.matcher(searchText);

int wordCount = 0;
while (matcher.find())
    wordCount++;

System.out.println(wordCount);

这也导致:

2

效率基准

那么,哪个效率最高? 让我们运行一个小基准测试:

int runs = 100000;

long start1 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithSplit(searchText, targetWord);
}

long end1 = System.currentTimeMillis();
System.out.println(String.format("Array split approach took: %s miliseconds", end1-start1));

long start2 = System.currentTimeMillis();
  for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithCollections(searchText, targetWord);
}

long end2 = System.currentTimeMillis();
System.out.println(String.format("Collections.frequency() approach took: %s miliseconds", end2-start2));

long start3 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithRegex(searchText, targetWord);
}

long end3 = System.currentTimeMillis();
System.out.println(String.format("Regex approach took: %s miliseconds", end3-start3));

每个方法将运行 100000 次(由于大数定律,数字越高,由于偶然性和结果导致的方差和结果越低)。 运行此代码会导致:

Array split approach took: 152 miliseconds
Collections.frequency() approach took: 140 miliseconds
Regex approach took: 92 miliseconds

但是——如果我们通过扩大搜索来使搜索的计算成本更高,会发生什么? 让我们生成一个合成句子:

List possibleWords = Arrays.asList("hello", "world ");
StringBuffer searchTextBuffer = new StringBuffer();

for (int i = 0; i < 100; i++) {
    searchTextBuffer.append(String.join(" ", possibleWords));
}
System.out.println(searchTextBuffer);

这将创建一个包含内容的字符串:

hello world hello world hello world hello ...

查看我们的 Git 学习实践指南,其中包含最佳实践、行业认可的标准以及随附的备忘单。 停止谷歌搜索 Git 命令,实际上 学习 它!

现在,如果我们要搜索“hello”或“world” - 匹配项将比之前的两个多得多。 我们的方法现在在基准测试中表现如何?

Array split approach took: 606 miliseconds
Collections.frequency() approach took: 899 miliseconds
Regex approach took: 801 miliseconds

现在,数组拆分最快! 一般来说,基准取决于各种因素——例如搜索空间、目标词等,您的个人用例可能与基准不同。

建议: 在您自己的文本上尝试这些方法,记下时间,然后为您选择最有效和最优雅的方法。

结论

在这个简短的指南中,我们了解了如何在 Java 的字符串中计算目标单词的单词出现次数。 我们首先拆分字符串并使用一个简单的计数器,然后使用 Collections 助手类,最后,使用正则表达式。

最后,我们对这些方法进行了基准测试,并注意到性能不是线性的,并且取决于搜索空间。 对于具有许多匹配项的较长输入文本,拆分数组似乎是最高效的。 自己尝试所有三种方法,然后选择性能最高的一种。

时间戳记:

更多来自 堆栈滥用