我在 Richard Bird 的书中读到这个问题:在 War and Peace(或任何其他与此相关的文本)中找到前五个最常见的单词。

这是我目前的尝试:

public class WarAndPeace { 
    public static void main(String[] args) throws Exception { 
        Map<String, Integer> wc = 
            Files.lines(Paths.get("/tmp", "/war-and-peace.txt")) 
            .map(line -> line.replaceAll("\\p{Punct}", "")) 
            .flatMap(line -> Arrays.stream(line.split("\\s+"))) 
            .filter(word -> word.matches("\\w+")) 
            .map(s -> s.toLowerCase()) 
            .filter(s -> s.length() >= 2) 
            .collect(Collectors.toConcurrentMap( 
                    w -> w, w -> 1, Integer::sum)); 
 
        wc.entrySet() 
            .stream() 
            .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())) 
            .limit(5) 
            .forEach(e -> System.out.println(e.getKey() + ": " + e.getValue())); 
 
    } 
} 

这绝对看起来很有趣并且运行得相当快。在我的笔记本电脑上,它打印以下内容:
$> time java -server -Xmx10g -cp target/classes tmp.WarAndPeace 
the: 34566 
and: 22152 
to: 16716 
of: 14987 
a: 10521 
java -server -Xmx10g -cp target/classes tmp.WarAndPeace  1.86s user 0.13s system 274% cpu 0.724 total 

它通常在 2 秒内运行。您能否从表现力和性能的角度对此提出进一步的改进建议?

PS:如果您对这个问题的丰富历史感兴趣,请参阅 here 。

请您参考如下方法:

您正在重新编译每一行和每个单词的所有正则表达式。而不是 .flatMap(line -> Arrays.stream(line.split("\\s+")))写 .flatMap(Pattern.compile("\\s+")::splitAsStream) . .filter(word -> word.matches("\\w+")) 也一样:使用 .filter(Pattern.compile("^\\w+$").asPredicate()) . map 也一样.

可能最好换.map(s -> s.toLowerCase())和 .filter(s -> s.length() >= 2)为了不打电话toLowerCase()对于一个字母的单词。

你不应该使用 Collectors.toConcurrentMap(w -> w, w -> 1, Integer::sum) .首先,您的流不是并行的,因此您可以轻松替换 toConcurrentMap与 toMap .其次,使用 Collectors.groupingBy(w -> w, Collectors.summingInt(w -> 1)) 可能更有效(尽管需要测试)因为这会减少装箱(但添加一个终结器步骤,将一次装箱所有值)。

而不是 (e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())您可以使用现成的比较器:Map.Entry.comparingByValue() (虽然可能是品味问题)。

总结一下:

Map<String, Integer> wc = 
    Files.lines(Paths.get("/tmp", "/war-and-peace.txt")) 
        .map(Pattern.compile("\\p{Punct}")::matcher) 
        .map(matcher -> matcher.replaceAll("")) 
        .flatMap(Pattern.compile("\\s+")::splitAsStream) 
        .filter(Pattern.compile("^\\w+$").asPredicate()) 
        .filter(s -> s.length() >= 2) 
        .map(s -> s.toLowerCase()) 
        .collect(Collectors.groupingBy(w -> w, 
                Collectors.summingInt(w -> 1))); 
 
wc.entrySet() 
    .stream() 
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())) 
    .limit(5) 
    .forEach(e -> System.out.println(e.getKey() + ": " + e.getValue())); 

如果您不喜欢方法引用(有些人不喜欢),您可以将预编译的正则表达式存储在变量中。


评论关闭
IT干货网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!