通过分组、计数和过滤操作收集流
Collect stream with grouping, counting and filtering operations
我正在尝试收集丢弃很少使用的物品的流,如本例所示:
import java.util.*;
import java.util.function.Function;
import static java.util.stream.Collectors.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import org.junit.Test;
@Test
public void shouldFilterCommonlyUsedWords() {
// given
List<String> allWords = Arrays.asList(
"call", "feel", "call", "very", "call", "very", "feel", "very", "any");
// when
Set<String> commonlyUsed = allWords.stream()
.collect(groupingBy(Function.identity(), counting()))
.entrySet().stream().filter(e -> e.getValue() > 2)
.map(Map.Entry::getKey).collect(toSet());
// then
assertThat(commonlyUsed, containsInAnyOrder("call", "very"));
}
我觉得可以做得更简单 - 对吗?
没有办法绕过创建 Map
,除非您想接受非常高的 CPU 复杂度。
不过,你可以去掉secondcollect
操作:
Map<String,Long> map = allWords.stream()
.collect(groupingBy(Function.identity(), HashMap::new, counting()));
map.values().removeIf(l -> l<=2);
Set<String> commonlyUsed=map.keySet();
请注意,在 Java 8 中,HashSet
仍包含 HashMap
,因此当您需要 HashMap
时,请使用 keySet()
=18=] 首先,考虑到当前的实施,不会浪费 space。
当然,如果感觉更“流畅”,您可以将 post 处理隐藏在 Collector
中:
Set<String> commonlyUsed = allWords.stream()
.collect(collectingAndThen(
groupingBy(Function.identity(), HashMap::new, counting()),
map-> { map.values().removeIf(l -> l<=2); return map.keySet(); }));
不久前,我 wrote 我的图书馆的一个实验性 distinct(atLeast)
方法:
public StreamEx<T> distinct(long atLeast) {
if (atLeast <= 1)
return distinct();
AtomicLong nullCount = new AtomicLong();
ConcurrentHashMap<T, Long> map = new ConcurrentHashMap<>();
return filter(t -> {
if (t == null) {
return nullCount.incrementAndGet() == atLeast;
}
return map.merge(t, 1L, (u, v) -> (u + v)) == atLeast;
});
}
所以我的想法是像这样使用它:
Set<String> commonlyUsed = StreamEx.of(allWords).distinct(3).toSet();
这执行了状态过滤,看起来有点难看。我怀疑这个功能是否有用,所以我没有将它合并到 master 分支中。尽管如此,它还是在单流通道中完成了工作。也许我应该复活它。同时你可以将这段代码复制到静态方法中并像这样使用它:
Set<String> commonlyUsed = distinct(allWords.stream(), 3).collect(Collectors.toSet());
更新(2015/05/31):我添加了distinct(atLeast)
method to the StreamEx 0.3.1. It's implemented using custom spliterator。基准测试表明,对于顺序流,此实现比上述有状态过滤快得多,而且在许多情况下,它也比本主题中提出的其他解决方案快。如果在流中遇到 null
,它也能很好地工作(groupingBy
收集器不支持 null
作为 class,因此基于 groupingBy
的解决方案将失败如果遇到null
)。
我个人更喜欢 Holger 的解决方案 (+1),但是,我不会从 groupingBy 映射中删除元素,而是 filter 它的 entrySet 和 map finalizer 中的 Set 的结果(对我来说感觉更 streamy)
Set<String> commonlyUsed = allWords.stream().collect(
collectingAndThen(
groupingBy(identity(), counting()),
(map) -> map.entrySet().stream().
filter(e -> e.getValue() > 2).
map(e -> e.getKey()).
collect(Collectors.toSet())));
我正在尝试收集丢弃很少使用的物品的流,如本例所示:
import java.util.*;
import java.util.function.Function;
import static java.util.stream.Collectors.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import org.junit.Test;
@Test
public void shouldFilterCommonlyUsedWords() {
// given
List<String> allWords = Arrays.asList(
"call", "feel", "call", "very", "call", "very", "feel", "very", "any");
// when
Set<String> commonlyUsed = allWords.stream()
.collect(groupingBy(Function.identity(), counting()))
.entrySet().stream().filter(e -> e.getValue() > 2)
.map(Map.Entry::getKey).collect(toSet());
// then
assertThat(commonlyUsed, containsInAnyOrder("call", "very"));
}
我觉得可以做得更简单 - 对吗?
没有办法绕过创建 Map
,除非您想接受非常高的 CPU 复杂度。
不过,你可以去掉secondcollect
操作:
Map<String,Long> map = allWords.stream()
.collect(groupingBy(Function.identity(), HashMap::new, counting()));
map.values().removeIf(l -> l<=2);
Set<String> commonlyUsed=map.keySet();
请注意,在 Java 8 中,HashSet
仍包含 HashMap
,因此当您需要 HashMap
时,请使用 keySet()
=18=] 首先,考虑到当前的实施,不会浪费 space。
当然,如果感觉更“流畅”,您可以将 post 处理隐藏在 Collector
中:
Set<String> commonlyUsed = allWords.stream()
.collect(collectingAndThen(
groupingBy(Function.identity(), HashMap::new, counting()),
map-> { map.values().removeIf(l -> l<=2); return map.keySet(); }));
不久前,我 wrote 我的图书馆的一个实验性 distinct(atLeast)
方法:
public StreamEx<T> distinct(long atLeast) {
if (atLeast <= 1)
return distinct();
AtomicLong nullCount = new AtomicLong();
ConcurrentHashMap<T, Long> map = new ConcurrentHashMap<>();
return filter(t -> {
if (t == null) {
return nullCount.incrementAndGet() == atLeast;
}
return map.merge(t, 1L, (u, v) -> (u + v)) == atLeast;
});
}
所以我的想法是像这样使用它:
Set<String> commonlyUsed = StreamEx.of(allWords).distinct(3).toSet();
这执行了状态过滤,看起来有点难看。我怀疑这个功能是否有用,所以我没有将它合并到 master 分支中。尽管如此,它还是在单流通道中完成了工作。也许我应该复活它。同时你可以将这段代码复制到静态方法中并像这样使用它:
Set<String> commonlyUsed = distinct(allWords.stream(), 3).collect(Collectors.toSet());
更新(2015/05/31):我添加了distinct(atLeast)
method to the StreamEx 0.3.1. It's implemented using custom spliterator。基准测试表明,对于顺序流,此实现比上述有状态过滤快得多,而且在许多情况下,它也比本主题中提出的其他解决方案快。如果在流中遇到 null
,它也能很好地工作(groupingBy
收集器不支持 null
作为 class,因此基于 groupingBy
的解决方案将失败如果遇到null
)。
我个人更喜欢 Holger 的解决方案 (+1),但是,我不会从 groupingBy 映射中删除元素,而是 filter 它的 entrySet 和 map finalizer 中的 Set 的结果(对我来说感觉更 streamy)
Set<String> commonlyUsed = allWords.stream().collect(
collectingAndThen(
groupingBy(identity(), counting()),
(map) -> map.entrySet().stream().
filter(e -> e.getValue() > 2).
map(e -> e.getKey()).
collect(Collectors.toSet())));