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

Whi*_*cal 25 java collections lambda java-8 java-stream

我已经测试了Java 8 lambdas和stream上的max()函数,看起来如果执行max(),即使多个对象比较为0,它也会返回绑定候选者中的任意元素进一步考虑.

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

例如:

//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.
Run Code Online (Sandbox Code Playgroud)

Stu*_*rks 35

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

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

如果不将至少部分结果存储在集合中,我不知道有任何方法可以做到这一点.

比如输入集合

List<String> list = ... ;
Run Code Online (Sandbox Code Playgroud)

它很简单,可以在两次传递中执行此操作,第一次获取最长的长度,第二次过滤具有该长度的字符串:

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

List<String> result = list.stream()
                          .filter(s -> s.length() == longest)
                          .collect(toList());
Run Code Online (Sandbox Code Playgroud)

如果输入是一个不能多次遍历的流,则可以使用收集器仅在一次通过中计算结果.编写这样的收集器并不困难,但由于有几种情况需要处理,因此有点单调乏味.在给定比较器的情况下生成这样的收集器的辅助函数如下:

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;
            }
        });
}
Run Code Online (Sandbox Code Playgroud)

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

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

Stream<String> input = ... ;

List<String> result = input.collect(maxList(comparing(String::length)));
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案.编写自己的收藏家非常强大,实际上非常简单,一旦你了解供应商/累加器/合并器/修整器命名法! (5认同)

Tag*_*eev 9

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Nic*_*tto 5

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

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

输出:

5
5
Run Code Online (Sandbox Code Playgroud)