如何强制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