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

我已经testing了Java 8 lambdaexpression式和stream的max()函数,并且似乎在max()被执行的情况下,即使多于一个对象比较为0,它也会返回绑定候选中的任意元素进一步考虑。

是否有一个明显的技巧或function,这样一个最大期望的行为,以便所有的最大值返回? 我没有看到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. 

我相信OP正在使用比较器将input分割成等价类,并且所需结果是根据该比较器最大等价类的成员列表。

不幸的是,使用int值作为示例问题是一个可怕的例子。 所有相等的int值都是可replace的,所以不存在保留等价值的顺序的概念。 也许一个更好的例子是使用string长度,其中期望的结果是从input中返回所有具有最长长度的string列表。

我不知道有什么办法做到这一点,而不至less在部分结果中存储部分结果。

给定一个input集合,说

 List<String> list = ... ; 

它足够简单,可以通过两遍来完成,第一个获得最长的长度,第二个过滤具有该长度的string:

 int longest = list.stream() .mapToInt(String::length) .max() .orElse(-1); List<String> result = list.stream() .filter(s -> s.length() == longest) .collect(toList()); 

如果input是一个不能被遍历多次的stream,则可以使用一个收集器仅仅一次计算结果。 编写这样一个收集器并不困难,但是由于有几个案例需要处理,所以有点繁琐。 给定一个比较器,产生这样一个收集器的帮助函数如下:

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

给定一个inputstream,这很容易使用:

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

如果我理解的很好,你需要Stream中max的频率。

实现这一目标的一种方法是,当您从Stream中收集元素时,将结果存储在TreeMap<Integer, List<Integer> 。 然后你抓住最后一个键(或者先取决于你给出的比较器)来获取包含最大值列表的值。

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

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

同样的精神的另一种方法是使用一个组合与counting()收集器的操作:

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

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

第一种方法需要存储stream中的所有元素。 第二个更好(见Holger的评论),因为中间List不是build立的。 在接近的时候,结果都是一次计算的。

如果您从集合中获取源,则可能需要使用Collections.max一次来查找Collections.frequency之后的最大值,以查找该值出现的次数。

它需要两遍,但使用更less的内存,因为您不必构build数据结构。

这个stream等价于coll.stream().max(...).get(...)后跟coll.stream().filter(...).count()

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

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

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

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

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

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

我不确定你是否正在努力

  • (a)找出最大项目的出现次数,或者
  • (b)在Comparator的情况下查找与equals不一致的所有最大值。

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

(b)的一个例子是:

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

你要给[Foo, foo, Foo] ,而不仅仅是FOO或者Optional[FOO]

在这两种情况下,都有一个巧妙的办法。 但是这些方法是有价值的,因为你需要跟踪不必要的信息。 例如,如果你从[2, 0, 2, 2, 1, 6, 2] ,那么只有当你达到6 ,你才会意识到没有必要跟踪所有的2秒。

我认为最好的方法是显而易见的。 使用max ,然后再次重复项目,把所有关系放到你select的集合中。 这对于(a)和(b)都是有效的。

我会这样做

 // I group by value and put it into a TreeMap then get the max value Stream.of(1,3,5,3,2,3,5).collect(groupingBy(Function.identity(), TreeMap::new, toList())) .lastEntry() .getValue() .forEach(System.out::println); 

输出:

 5 5