按第一个字符对单词进行分组

grouping words by first character

我有: 逐行读取的文本文件。每个字符串包含一行。

我想要的: 使用 Java 流按第一个字符对所有单词进行分组。

我目前有:

public static Map<Character, List<String>> groupByFirstChar(String fileName)
        throws IOException {

    return Files.lines(Paths.get(PATH)).
            flatMap(s -> Stream.of(s.split("[^a-zA-Z]"))).
            map(s -> s.toLowerCase()).
            sorted((s1, s2) -> s1.compareTo(s2)).
            collect(Collectors.groupingBy(s -> s.charAt(0)));
}

问题:我得到一个异常

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 0
at java.lang.String.charAt(String.java:646)
at textana.TextAnalysisFns.lambda(TextAnalysisFns.java:110)
at textana.TextAnalysisFns$$Lambda/159413332.apply(Unknown Source)
at java.util.stream.Collectors.lambda$groupingBy6(Collectors.java:907)
at java.util.stream.Collectors$$Lambda/189568618.accept(Unknown Source)
at java.util.stream.ReduceOpsReducingSink.accept(ReduceOps.java:169)
at java.util.stream.SortedOps$RefSortingSink$$Lambda/186370029.accept(Unknown Source)
at java.util.ArrayList.forEach(ArrayList.java:1249)
at java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:390)
at java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at textana.TextAnalysisFns.groupByFirstChar(TextAnalysisFns.java:110)
at textana.SampleTextAnalysisApp.main(SampleTextAnalysisApp.java:95)

问题:为什么我会得到 StringIndexOutOfBoundException?

根据评论中的提示解决:

public static Map<Character, List<String>> groupByFirstChar(String fileName)
        throws IOException {

    return Files.lines(Paths.get(PATH)).
            flatMap(s -> Stream.of(s.split("[^a-zA-Z]"))).
            filter(s -> s.length() > 0).
            map(s -> s.toLowerCase()).
            collect(Collectors.groupingBy(s -> s.charAt(0)));
}

用户 Eran 的解决方案一开始会给我空字符串,这是我不想要的。

您的文件末尾很可能有一个空行,可能是由您的文本编辑器悄悄添加的,这使得最后一个 s.charAt(0) 失败。

关于如何检测它的提示:在堆栈跟踪中,阅读 collectlambda

 s.charAt(0) 

在执行这条指令之前检查s是否为null以避免异常。

尝试过滤空字符串 "",因为它们没有导致 charAt(0) 抛出此异常的第一个字符。

您可以使用

flatMap(s -> Stream.of(s.split("[^a-zA-Z]"))).
filter(s -> !s.trim().isEmpty()). //add this line

顺便说一句,您的方法可能应该使用其 fileName 参数。所以也许可以考虑将 Paths.get(PATH) 改成更像

的东西
Paths.get(fileName).

Paths.get(PATH).resolve(fileName)

正如评论中已经提到的那样,因为您没有更改默认比较顺序,所以您不需要显式编写

sorted((s1, s2) -> s1.compareTo(s2))

但简单

sorted()

同样适用,因为此处将应用默认顺序。


@Alexis C. 所述,groupBy 将 return HashMap 这意味着您的密钥将不会被排序。如果你还想保留他们的顺序,你可以使用 groupBy 和 LinkedHashMap like

.collect(Collectors.groupingBy(s -> s.charAt(0), LinkedHashMap::new, Collectors.toList()));