且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何强制max返回Java Stream中的所有最大值?

更新时间:2022-06-20 22:36:37

我认为OP正在使用Comparator将输入划分为等效类,并且期望的结果是等效类的成员列表,即根据Comparator的最大值.

I believe the OP is using a Comparator to partition the input into equivalence classes, and the desired result is a list of members of the equivalence class that is the maximum according to that Comparator.

不幸的是,使用int值作为样本问题是一个可怕的例子.所有相等的int值都是可替代的,因此没有保留等效值顺序的概念.也许更好的例子是使用字符串长度,其中所需的结果是从输入中返回所有在该输入中具有最长长度的字符串列表.

Unfortunately, using int values as a sample problem is a terrible example. All equal int values are fungible, so there is no notion of preserving the ordering of equivalent values. Perhaps a better example is using string lengths, where the desired result is to return a list of strings from an input that all have the longest length within that input.

在不将至少部分结果存储在集合中的情况下,我不知道要怎么做.

I don't know of any way to do this without storing at least partial results in a collection.

给出一个输入集合,例如

Given an input collection, say

List<String> list = ... ;

...很简单,只需两遍即可完成此操作,第一遍获取最长的长度,第二遍筛选具有该长度的字符串:

...it's simple enough to do this in two passes, the first to get the longest length, and the second to filter the strings that have that length:

int longest = list.stream()
                  .mapToInt(String::length)
                  .max()
                  .orElse(-1);

List<String> result = list.stream()
                          .filter(s -> s.length() == longest)
                          .collect(toList());

如果输入是流,则不能遍历 一次,则可以使用收集器仅一次完成计算结果.编写这样的收集器并不困难,但是由于要处理几种情况,所以这有点乏味.给定Comparator的辅助函数可以生成这样的收集器,如下所示:

If the input is a stream, which cannot be traversed more than once, it is possible to compute the result in only a single pass using a collector. Writing such a collector isn't difficult, but it is a bit tedious as there are several cases to be handled. A helper function that generates such a collector, given a Comparator, is as follows:

static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) {
    return Collector.of(
        ArrayList::new,
        (list, t) -> {
            int c;
            if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) {
                list.add(t);
            } else if (c > 0) {
                list.clear();
                list.add(t);
            }
        },
        (list1, list2) -> {
            if (list1.isEmpty()) {
                return list2;
            } 
            if (list2.isEmpty()) {
                return list1;
            }
            int r = comp.compare(list1.get(0), list2.get(0));
            if (r < 0) {
                return list2;
            } else if (r > 0) {
                return list1;
            } else {
                list1.addAll(list2);
                return list1;
            }
        });
}

这会将中间结果存储在ArrayList中.不变的是,任何此类列表中的所有元素在Comparator方面都是等效的.当添加一个元素时,如果它小于列表中的元素,则将其忽略;否则,它将被忽略.如果相等,则将其添加;如果更大,则清空列表并添加新元素.合并也不太困难:返回具有较大元素的列表,但是如果它们的元素相等,则会添加列表.

This stores intermediate results in an ArrayList. The invariant is that all elements within any such list are equivalent in terms of the Comparator. When adding an element, if it's less than the elements in the list, it's ignored; if it's equal, it's added; and if it's greater, the list is emptied and the new element is added. Merging isn't too difficult either: the list with the greater elements is returned, but if their elements are equal the lists are appended.

给出输入流,这很容易使用:

Given an input stream, this is pretty easy to use:

Stream<String> input = ... ;

List<String> result = input.collect(maxList(comparing(String::length)));