Ked*_*ade 5 java-8 java-stream
我读理查德·伯德的书这样的问题:找到五种最常用的词在战争与和平(或任何其他文本为此事).
这是我目前的尝试:
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()));
}
}
Run Code Online (Sandbox Code Playgroud)
这绝对看起来很有趣并且运行得相当快.在我的笔记本电脑上打印以下内容:
$> 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
Run Code Online (Sandbox Code Playgroud)
它通常在2秒内运行.你能从表现力和表现的角度建议进一步改进吗?
PS:如果您对此问题的丰富历史感兴趣,请参阅此处.
Tag*_*eev 10
您正在重新编译每行和每个单词的所有正则表达式.而不是.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()));
Run Code Online (Sandbox Code Playgroud)
如果您不喜欢方法引用(有些人不喜欢),则可以将预编译的regexp存储在变量中.
您正在执行多项冗余和不必要的操作.
然后,您通过接受仅由单词字符组成的字符串来过滤拆分结果.由于您已经删除了所有标点符号和间距字符,因此将对具有字符,空格或标点字符的字符串进行排序,并且我不确定这是否是预期的逻辑.毕竟,如果你只对单词感兴趣,为什么不首先搜索单词呢?由于Java 8不支持匹配流,因此我们可以使用非单词字符作为边界来指示它进行拆分.
然后你正在做一个.map(s -> s.toLowerCase()).filter(s -> s.length() >= 2).因为对于英文文本,字符串长度在将其更改为大写时不会改变,过滤条件不受影响,因此我们可以先过滤,跳过toLowerCase谓词不接受的字符串的转换:.filter(s -> s.length() >= 2).map(s -> s.toLowerCase()).净收益可能很小,但不会受到伤害.
选择正确的Collector.Tagir已经解释过了.原则上,Collectors.counting()哪个更适合Collectors.summingInt(w->1),但不幸的是,Oracle的当前实现很差,因为它基于所有元素的reduce拆箱和重新装箱Long.
把它们放在一起,你会得到:
Files.lines(Paths.get("/tmp", "/war-and-peace.txt"))
.flatMap(Pattern.compile("\\W+")::splitAsStream)
.filter(s -> s.length() >= 2)
.map(String::toLowerCase)
.collect(Collectors.groupingBy(w->w, Collectors.summingInt(w->1)))
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(5)
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
Run Code Online (Sandbox Code Playgroud)
如上所述,如果单词计数略高于您的方法,请不要感到惊讶.
| 归档时间: |
|
| 查看次数: |
975 次 |
| 最近记录: |