当前位置: 首页 > 知识库问答 >
问题:

通过分组、计数和过滤操作收集流

司空宣
2023-03-14

我试图收集流,丢弃很少使用的项目,如本例所示:

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"));
}

我有一种感觉,可以做得简单得多 - 我是对的吗?

共有3个答案

魏元白
2023-03-14

我个人更喜欢Holger的解决方案(1),但是,我不会从groupingBy map中删除元素,而是过滤其条目Set并将结果映射到终结器中的Set(这对我来说感觉更加流畅)

    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())));
公子昂
2023-03-14

不久前,我为我的库编写了一个实验性的<code>distinct(至少)</code>方法:

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();

这执行了一个状态过滤,看起来有点难看。我怀疑这样的特性是否有用,所以我没有把它合并到主分支中。不过它在单流通道中完成工作。或许我应该让它复活。同时,您可以将这段代码复制到静态方法中,并像这样使用它:

Set<String> commonlyUsed = distinct(allWords.stream(), 3).collect(Collectors.toSet());

更新(2015/05/31):我在StreamEx 0.3.1中添加了不同(at最少)方法。它是使用自定义拆分器实现的。基准测试表明,这种实现对于顺序流来说比上述状态过滤要快得多,并且在许多情况下它也比本主题中提出的其他解决方案更快。此外,如果在流中遇到null,它也会很好地工作(groupingBy收集器不支持null作为类,因此如果遇到null,基于groupingBy解决方案将失败)。

司徒志
2023-03-14

除非您希望接受非常高的CPU复杂度,否则无法创建<code>映射。

但是,您可以删除第二个< code>collect操作:

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中

当然,如果感觉更“流畅”,您可以在< code >收集器中隐藏后处理:

Set<String> commonlyUsed = allWords.stream()
    .collect(collectingAndThen(
        groupingBy(Function.identity(), HashMap::new, counting()),
        map-> { map.values().removeIf(l -> l<=2); return map.keySet(); }));
 类似资料:
  • drop 返回包含去掉前n个元素的所有元素的列表。 assertEquals(listOf(5, 6), list.drop(4)) dropWhile 返回根据给定函数从第一项开始去掉指定元素的列表。 assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 }) dropLastWhile 返回根据给定函数从最后一项开始去掉指定元素的列表

  • 有没有办法把这两条流合并成一条?我使用第一个流在嵌套列表中进行过滤和查找,并使用第二个流根据流的结果创建地图。我想知道是否有一种方法可以用一条流来实现这一点。 像这样的

  • TakeLast 发射Observable发射的最后N项数据 使用TakeLast操作符修改原始Observable,你可以只发射Observable’发射的后N项数据,忽略前面的数据。 taskLast.n 使用takeLast操作符,你可以只发射原始Observable发射的后N项数据,忽略之前的数据。注意:这会延迟原始Observable发射的任何数据项,直到它全部完成。 takeLast的

  • Take 只发射前面的N项数据 使用Take操作符让你可以修改Observable的行为,只返回前面的N项数据,然后发射完成通知,忽略剩余的数据。 RxJava将这个操作符实现为take函数。 如果你对一个Observable使用take(n)(或它的同义词limit(n))操作符,而那个Observable发射的数据少于N项,那么take操作生成的Observable不会抛异常或发射onErro

  • SkipLast 抑制Observable发射的后N项数据 使用SkipLast操作符修改原始Observable,你可以忽略Observable’发射的后N项数据,只保留前面的数据。 使用SkipLast操作符,你可以忽略原始Observable发射的后N项数据,只保留之前的数据。注意:这个机制是这样实现的:延迟原始Observable发射的任何数据项,直到它发射了N项数据。 skipLast的

  • Skip 抑制Observable发射的前N项数据 使用Skip操作符,你可以忽略Observable’发射的前N项数据,只保留之后的数据。 RxJava中这个操作符叫skip。skip的这个变体默认不在任何特定的调度器上执行。 Javadoc: skip(int)) skip的这个变体接受一个时长而不是数量参数。它会丢弃原始Observable开始的那段时间发射的数据,时长和时间单位通过参数指定