如何使用Java 8在文本文件中查找单词总数,元音总数,特殊字符总数

时间:2019-03-25 10:22:33

标签: java lambda java-8 java-stream

我有一个文本文件,我想检查
 -文件中的单词总数
 -文件中的元音总数
 -文件中的特殊字符总数

使用Java 8流。

如果可能的话,我希望在一次迭代中将其输出为Map,即

{"totalWordCount":10,"totalVowelCount":10,"totalSpecialCharacter":10}

我尝试了以下代码

    Long wordCount=Files.lines(child).parallel().flatMap(line -> Arrays.stream(line.trim().split(" ")))
                            .map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase().trim())
                            .filter(word -> !word.isEmpty())
                            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).values().stream().reduce(0L, Long::sum)

但是它只给我总的字数,我在想是否有可能返回一个包含以上所有输出的单张地图。

1 个答案:

答案 0 :(得分:2)

如果我们只需要计算特殊字符和元音,则可以使用以下内容:

Map<String,Long> result;
try(Stream<String> lines = Files.lines(path)) {
    result = lines
        .flatMap(Pattern.compile("\\s+")::splitAsStream)
        .flatMapToInt(String::chars)
        .filter(c -> !Character.isAlphabetic(c) || "aeiou".indexOf(c) >= 0)
        .mapToObj(c -> "aeiou".indexOf(c)>=0? "totalVowelCount": "totalSpecialCharacter")
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

首先,我们将行流平整为单词流,然后平整为字符流,以按其类型对它们进行分组。由于“特殊字符”和“元音”是互斥的,因此可以顺利进行。原则上,如果我们仅将过滤器扩展为跳过空白字符,则可以省略对单词的拼合,但是在这里,这有助于获得对单词进行计数的解决方案。

由于单词是与字符不同的实体,因此在同一操作中对单词进行计数并不是那么简单。一种解决方案是为每个单词注入一个伪字符,并像在末尾的其他字符一样对其进行计数。由于所有实际字符都是正数,因此我们可以使用-1

Map<String,Long> result;
try(Stream<String> lines = Files.lines(path)) {
    result = lines.flatMap(Pattern.compile("\\s+")::splitAsStream)
        .flatMapToInt(w -> IntStream.concat(IntStream.of(-1), w.chars()))
        .mapToObj(c -> c==-1? "totalWordCount": "aeiou".indexOf(c)>=0? "totalVowelCount":
                Character.isAlphabetic(c)? "totalAlphabetic": "totalSpecialCharacter")
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

这会在结果映射表中除其他类别外添加一个"totalAlphabetic"类别。如果不希望这样,可以在.filter(cat -> !cat.equals("totalAlphabetic"))mapToObj步骤之间插入一个collect步骤。或在mapToObj步骤之前,使用第一种解决方案中的过滤器。

另外请注意,此解决方案的工作量超出了必要,因为它将输入分为几行,这是不必要的,因为我们可以像其他空白一样将换行符视为换行符,即作为单词边界。从Java 9开始,我们可以使用Scanner来完成工作:

Map<String,Long> result;
try(Scanner scanner = new Scanner(path)) {
    result = scanner.findAll("\\S+")
        .flatMapToInt(w -> IntStream.concat(IntStream.of(-1), w.group().chars()))
        .mapToObj(c -> c==-1? "totalWordCount": "aeiou".indexOf(c)>=0? "totalVowelCount":
                Character.isAlphabetic(c)? "totalAlphabetic": "totalSpecialCharacter")
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

这将首先将输入拆分为单词,而无需特别处理换行符。 This answer包含Scanner.findAll的Java 8兼容实现。

上述解决方案将既不是空格也不是字母的每个字符都视为“特殊字符”。如果您对“特殊字符”的定义不同,则适应解决方案应该不太困难。