Java 8 Stream API - 选择分组后的最低密钥

Jam*_*eeh 16 java java-8 java-stream

我有一个Foo对象流.

class Foo {
    private int variableCount;
    public Foo(int vars) {
        this.variableCount = vars; 
    }
    public Integer getVariableCount() { 
      return variableCount; 
    }
}
Run Code Online (Sandbox Code Playgroud)

我想要一个列表,Foo它们都具有最低的variableCount.

例如

new Foo(3), new Foo(3), new Foo(2), new Foo(1), new Foo(1)
Run Code Online (Sandbox Code Playgroud)

我只希望流返回最后2 Foo

我试过用分组进行收集

.collect(Collectors.groupingBy((Foo foo) -> {
                    return foo.getVariableCount();
})
Run Code Online (Sandbox Code Playgroud)

这会返回一个Map<Integer, List<Foo>>,我不知道如何将其转换为我想要的.

提前致谢

lex*_*ore 14

您可以使用有序地图进行分组,然后只获取第一个条目.一些事情:

Collectors.groupingBy(
    Foo::getVariableCount,
    TreeMap::new,
    Collectors.toList())
.firstEntry()
.getValue()
Run Code Online (Sandbox Code Playgroud)

  • @JamesKleeh是的,这不是最理想的,但可以使用OOTB.我认为应该有一个更好的解决方案与自定义收集器. (4认同)

rge*_*man 10

这是一个解决方案:

  1. 只列出一次列表.
  2. 不构建包含所有输入项的映射或其他结构(除非变量计数完全相同),只保留当前最小的那些.
  3. 是O(n)时间,O(n)空间.完全可能所有Foos都具有相同的变量计数,在这种情况下,此解决方案将存储所有项目,如其他解决方案.但在实践中,由于具有不同的,不同的值和更高的基数,列表中的项目数量可能会低得多.

编辑

我根据评论中的建议改进了我的解决方案.

我实现了一个累加器对象,它Collector为此提供了函数.

/**
 * Accumulator object to hold the current min
 * and the list of Foos that are the min.
 */
class Accumulator {
    Integer min;
    List<Foo> foos;

    Accumulator() {
        min = Integer.MAX_VALUE;
        foos = new ArrayList<>();
    }

    void accumulate(Foo f) {
        if (f.getVariableCount() != null) {
            if (f.getVariableCount() < min) {
                min = f.getVariableCount();
                foos.clear();
                foos.add(f);
            } else if (f.getVariableCount() == min) {
                foos.add(f);
            }
        }
    }

    Accumulator combine(Accumulator other) {
        if (min < other.min) {
            return this;
        }
        else if (min > other.min) {
            return other;
        }
        else {
            foos.addAll(other.foos);
            return this;
        }
    }

    List<Foo> getFoos() { return foos; }
}
Run Code Online (Sandbox Code Playgroud)

然后我们要做的就是collect引用累加器的函数方法.

List<Foo> mins = foos.stream().collect(Collector.of(
    Accumulator::new,
    Accumulator::accumulate,
    Accumulator::combine,
    Accumulator::getFoos
    )
);
Run Code Online (Sandbox Code Playgroud)

测试用

List<Foo> foos = Arrays.asList(new Foo(3), new Foo(3), new Foo(2), new Foo(1), new Foo(1), new Foo(4));
Run Code Online (Sandbox Code Playgroud)

输出是(有一个合适的toString定义Foo):

[Foo{1}, Foo{1}]
Run Code Online (Sandbox Code Playgroud)

  • 您的收藏家的特征是完全不合理的.你的收集器不是'CONCURRENT`,因为它使用了一个非线程安全的`ArrayList`.由于它维护了顺序,因此指定"UNORDERED"会破坏该有用属性.除此之外,使用"EnumSet"将是直截了当的.但请注意,很少需要实现`Collector`接口.使用`Collector.of(Accumulator :: new,你的累加器函数,你的组合函数,Accumulator :: getFoos)就足够了.你的组合器必须考虑"min"仍然是"null"的可能性. (6认同)
  • 很好的答案,但1关于你的'O(最小计数)空间的评论.如果你输入了一百万个`Foo(2)`然后是一个`Foo(1)`,你仍然会先存储那百万个`Foo(2)`元素. (4认同)

Eug*_*ene 6

如果你可以流式传输(迭代)两次:

private static List<Foo> mins(List<Foo> foos) {
    return foos.stream()
            .map(Foo::getVariableCount)
            .min(Comparator.naturalOrder())
            .map(x -> foos.stream()
                          .filter(y -> y.getVariableCount() == x)
                          .collect(Collectors.toList()))
            .orElse(Collections.emptyList());
}
Run Code Online (Sandbox Code Playgroud)