且构网

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

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

更新时间:2022-06-20 22:41:07

我相信OP正在使用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.

给定一个输入集合,例如

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:

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 。不变量是任何这样的列表内的所有元素在比较器方面是等效的。当添加一个元素,如果它小于列表中的元素,它被忽略;如果相等,就添加;如果更大,则清空列表并添加新元素。合并也不是太困难:返回具有较大元素的列表,但如果它们的元素相等,则添加列表。

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