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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29334404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 15:04:45  来源:igfitidea点击:

How to force max to return ALL maximum values in a Java Stream?

javacollectionslambdajava-8java-stream

提问by Whimusical

I've tested a bit the maxfunction on Java 8 lambdas and streams, and it seems that in case maxis executed, even if more than one object compares to 0, it returns an arbitrary element within the tied candidates without further consideration.

我已经max在 Java 8 lambdas 和流上测试了一些函数,似乎在max执行的情况下,即使多个对象与 0 比较,它也会返回绑定候选中的任意元素,而无需进一步考虑。

Is there an evident trick or function for such a max expected behavior, so that all max values are returned? I don't see anything in the API but I am sure it must exist something better than comparing manually.

对于这种最大预期行为,是否有明显的技巧或功能,以便返回所有最大值?我在 API 中没有看到任何东西,但我确信它一定存在比手动比较更好的东西。

For instance:

例如:

// myComparator is an IntegerComparator
Stream.of(1, 3, 5, 3, 2, 3, 5)
    .max(myComparator)
    .forEach(System.out::println);
// Would print 5, 5 in any order.

回答by Stuart Marks

I believe the OP is using a Comparatorto 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.

我相信 OP 正在使用 aComparator将输入划分为等价类,并且期望的结果是等价类的成员列表,该列表是最大的等价类Comparator

Unfortunately, using intvalues as a sample problem is a terrible example. All equal intvalues 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.

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

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

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:

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

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

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.

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

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

给定一个输入流,这很容易使用:

Stream<String> input = ... ;

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

回答by Tagir Valeev

I implemented more generic collector solution with custom downstream collector. Probably some readers might find it useful:

我使用自定义下游收集器实现了更通用的收集器解决方案。可能有些读者会觉得它很有用:

public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator, 
                                                  Collector<? super T, A, D> downstream) {
    Supplier<A> downstreamSupplier = downstream.supplier();
    BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
    BinaryOperator<A> downstreamCombiner = downstream.combiner();
    class Container {
        A acc;
        T obj;
        boolean hasAny;

        Container(A acc) {
            this.acc = acc;
        }
    }
    Supplier<Container> supplier = () -> new Container(downstreamSupplier.get());
    BiConsumer<Container, T> accumulator = (acc, t) -> {
        if(!acc.hasAny) {
            downstreamAccumulator.accept(acc.acc, t);
            acc.obj = t;
            acc.hasAny = true;
        } else {
            int cmp = comparator.compare(t, acc.obj);
            if (cmp > 0) {
                acc.acc = downstreamSupplier.get();
                acc.obj = t;
            }
            if (cmp >= 0)
                downstreamAccumulator.accept(acc.acc, t);
        }
    };
    BinaryOperator<Container> combiner = (acc1, acc2) -> {
        if (!acc2.hasAny) {
            return acc1;
        }
        if (!acc1.hasAny) {
            return acc2;
        }
        int cmp = comparator.compare(acc1.obj, acc2.obj);
        if (cmp > 0) {
            return acc1;
        }
        if (cmp < 0) {
            return acc2;
        }
        acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc);
        return acc1;
    };
    Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc);
    return Collector.of(supplier, accumulator, combiner, finisher);
}

So by default it can be collected to list:

所以默认情况下可以收集到列表:

public static <T> Collector<T, ?, List<T>> maxAll(Comparator<? super T> comparator) {
    return maxAll(comparator, Collectors.toList());
}

But you can use other downstream collectors as well:

但是您也可以使用其他下游收集器:

public static String joinLongestStrings(Collection<String> input) {
    return input.stream().collect(
            maxAll(Comparator.comparingInt(String::length), Collectors.joining(","))));
}

回答by Nicolas Filotto

I would group by value and store the values into a TreeMapin order to have my values sorted, then I would get the max value by getting the last entry as next:

我将按值分组并将值存储到 aTreeMap中以便对我的值进行排序,然后我将通过获取最后一个条目作为下一个条目来获得最大值:

Stream.of(1, 3, 5, 3, 2, 3, 5)
    .collect(groupingBy(Function.identity(), TreeMap::new, toList()))
    .lastEntry()
    .getValue()
    .forEach(System.out::println);

Output:

输出:

5
5

回答by Alexis C.

If I understood well, you want the frequency of the maxvalue in the Stream.

如果我理解得很好,您需要maxStream 中值的频率。

One way to achieve that would be to store the results in a TreeMap<Integer, List<Integer>when you collect elements from the Stream. Then you grab the last key (or first depending on the comparator you give) to get the value which will contains the list of max values.

实现这一目标的一种方法是在TreeMap<Integer, List<Integer>从 Stream 收集元素时将结果存储在 a 中。然后您获取最后一个键(或第一个键,取决于您提供的比较器)以获取包含最大值列表的值。

List<Integer> maxValues = st.collect(toMap(i -> i,
                     Arrays::asList,
                     (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()),
                     TreeMap::new))
             .lastEntry()
             .getValue();

Collecting it from the Stream(4, 5, -2, 5, 5)will give you a List [5, 5, 5].

收集它Stream(4, 5, -2, 5, 5)会给你一个List [5, 5, 5]

Another approach in the same spirit would be to use a group by operation combined with the counting()collector:

本着相同精神的另一种方法是将 group by 操作与counting()收集器结合使用:

Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
                TreeMap::new,
                counting())).lastEntry(); //5=3 -> 5 appears 3 times

Basically you firstly get a Map<Integer, List<Integer>>. Then the downstream counting()collector will return the number of elements in each list mapped by its key resulting in a Map. From there you grab the max entry.

基本上你首先得到一个Map<Integer, List<Integer>>. 然后下游counting()收集器将返回由其键映射的每个列表中的元素数量,从而产生一个 Map。从那里您可以获取最大条目。

The first approaches require to store all the elements from the stream. The second one is better (see Holger's comment) as the intermediate Listis not built. In both approached, the result is computed in a single pass.

第一种方法需要存储流中的所有元素。第二个更好(请参阅 Holger 的评论),因为List没有构建中间体。在这两种方法中,结果都是在一次通过中计算的。

If you get the source from a collection, you may want to use Collections.maxone time to find the maximum value followed by Collections.frequencyto find how many times this value appears.

如果您从集合中获取源,您可能希望使用Collections.max一次来查找最大值,然后Collections.frequency查找此值出现的次数。

It requires two passes but uses less memory as you don't have to build the data-structure.

它需要两次传递但使用较少的内存,因为您不必构建数据结构。

The stream equivalent would be coll.stream().max(...).get(...)followed by coll.stream().filter(...).count().

流等效项coll.stream().max(...).get(...)后跟coll.stream().filter(...).count().

回答by Paul Boddington

I'm not really sure whether you are trying to

我不确定你是否正在尝试

  • (a) find the number of occurrences of the maximum item, or
  • (b) Find all the maximum values in the case of a Comparatorthat is not consistent with equals.
  • (a) 找出最大项出现的次数,或
  • (b) 找出 aComparator与 不一致的情况下的所有最大值equals

An example of (a) would be [1, 5, 4, 5, 1, 1] -> [5, 5].

(a) 的一个例子是[1, 5, 4, 5, 1, 1] -> [5, 5]

An example of (b) would be:

(b) 的一个例子是:

Stream.of("Bar", "FOO", "foo", "BAR", "Foo")
      .max((s, t) -> s.toLowerCase().compareTo(t.toLowerCase()));

which you want to give [Foo, foo, Foo], rather than just FOOor Optional[FOO].

你想给的[Foo, foo, Foo],而不仅仅是FOOor Optional[FOO]

In both cases, there are clever ways to do it in just one pass. But these approaches are of dubious value because you would need to keep track of unnecessary information along the way. For example, if you start with [2, 0, 2, 2, 1, 6, 2], it would only be when you reach 6that you would realise it was not necessary to track all the 2s.

在这两种情况下,都有巧妙的方法可以一次性完成。但是这些方法的价值值得怀疑,因为您需要在此过程中跟踪不必要的信息。例如,如果您从 开始[2, 0, 2, 2, 1, 6, 2],那么只有当您到达时6,您才会意识到没有必要跟踪所有2s。

I think the best approach is the obvious one; use max, and then iterate the items again putting all the ties into a collection of your choice. This will work for both (a) and (b).

我认为最好的方法是显而易见的方法;使用max,然后再次迭代项目,将所有关系放入您选择的集合中。这对 (a) 和 (b) 都适用。