如何强制 return Java 流中的所有最大值?
How to force max to return ALL maximum values in a Java Stream?
我已经在 Java 8 个 lambda 和流上测试了 max
函数,似乎万一 max
被执行,即使有多个对象比较到 0,它 returns 并列候选中的任意元素,无需进一步考虑。
对于这样的最大预期行为,是否有明显的技巧或函数,以便返回所有最大值?我在 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.
如果我理解的很好,你想要 Stream 中 max
值的频率。
实现此目的的一种方法是在从流中收集元素时将结果存储在 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)
收集它会给你一个 List [5, 5, 5]
。
本着相同精神的另一种方法是结合 counting()
收集器使用 group by 操作:
Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
TreeMap::new,
counting())).lastEntry(); //5=3 -> 5 appears 3 times
基本上你首先得到一个Map<Integer, List<Integer>>
。然后下游 counting()
收集器将 return 每个列表中由其键映射的元素数量,从而生成一个 Map。从那里您可以获取最大条目。
第一种方法需要存储流中的所有元素。第二个更好(请参阅 Holger 的评论),因为未构建中间 List
。在这两种方法中,结果都是在一次通过中计算出来的。
如果您从集合中获取来源,您可能需要使用 Collections.max
一次来查找最大值,然后使用 Collections.frequency
来查找该值出现了多少次。
它需要两次传递,但使用较少的内存,因为您不必构建数据结构。
等效的流是 coll.stream().max(...).get(...)
后跟 coll.stream().filter(...).count()
。
我不太确定你是否想
- (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
,然后再次迭代这些项目,将所有关系放入您选择的集合中。这将适用于 (a) 和 (b)。
我相信 OP 使用 Comparator
将输入划分为等价 classes,并且期望的结果是等价 class 的成员列表,即最大根据 Comparator
.
不幸的是,使用 int
值作为示例问题是一个糟糕的例子。所有相等的 int
值都是可替代的,因此没有保留等值值顺序的概念。也许更好的例子是使用字符串长度,其中所需的结果是 return 来自输入的字符串列表,这些字符串在该输入中都具有最长的长度。
如果不将至少部分结果存储在集合中,我不知道有什么方法可以做到这一点。
给定一个输入集合,说
List<String> list = ... ;
...这很简单,分两次完成,第一次获得最长的长度,第二次过滤具有该长度的字符串:
int longest = list.stream()
.mapToInt(String::length)
.max()
.orElse(-1);
List<String> result = list.stream()
.filter(s -> s.length() == longest)
.collect(toList());
如果输入是一个流,,则可以使用收集器仅通过一次计算结果。编写这样的收集器并不难,但是有点乏味,因为要处理多种情况。给定一个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;
}
});
}
这将中间结果存储在 ArrayList
中。不变量是任何此类列表中的所有元素在 Comparator
方面都是等效的。添加元素时,如果小于列表中的元素,则忽略;如果相等,则添加;如果它更大,则清空列表并添加新元素。合并也不是太困难:具有更大元素的列表是 returned,但如果它们的元素相等,则列表被附加。
给定一个输入流,这很容易使用:
Stream<String> input = ... ;
List<String> result = input.collect(maxList(comparing(String::length)));
我使用自定义下游收集器实现了更通用的收集器解决方案。可能有些读者会觉得有用:
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(","))));
}
我将按值分组并将值存储到 TreeMap
中以便对我的值进行排序,然后我将通过获取最后一个条目作为下一个来获得最大值:
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(groupingBy(Function.identity(), TreeMap::new, toList()))
.lastEntry()
.getValue()
.forEach(System.out::println);
输出:
5
5
如果您更愿意依赖库而不是此处的其他答案,StreamEx has a collector 可以做到这一点。
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(MoreCollectors.maxAll())
.forEach(System.out::println);
对于没有自然排序(即不实现 Comparable)的项目流,也有 a version which takes a Comparator。
System.out.println(
Stream.of(1, 3, 5, 3, 2, 3, 5)
.map(a->new Integer[]{a})
.reduce((a,b)->
a[0]==b[0]?
Stream.concat(Stream.of(a),Stream.of(b)).toArray() :
a[0]>b[0]? a:b
).get()
)
我一直在寻找关于这个问题的好答案,但有点复杂,在我自己弄清楚之前找不到任何东西,这就是为什么我发帖如果这对任何人有帮助。
我有一份小猫名单。
小猫是一个有名字、年龄和性别的对象。我必须 return 列出所有最小的小猫。
例如:
所以小猫列表将包含小猫对象 (k1, k2, k3, k4),它们的年龄相应地为 (1, 2, 3, 1)。我们要return[k1,k4],因为他们都是最小的。如果只有一个最小的存在,函数应该 return [k1(youngest)].
查找列表的最小值(如果存在):
Optional<Kitten> minKitten = kittens.stream().min(Comparator.comparingInt(Kitten::getAge));
按最小值筛选列表
return minKitten.map(value -> kittens.stream().filter(kitten -> kitten.getAge() == value.getAge())
.collect(Collectors.toList())).orElse(Collections.emptyList());
以下两行将在不实现单独的比较器的情况下完成:
List<Integer> list = List.of(1, 3, 5, 3, 2, 3, 5);
list.stream().filter(i -> i == (list.stream().max(Comparator.comparingInt(i2 -> i2))).get()).forEach(System.out::println);
我已经在 Java 8 个 lambda 和流上测试了 max
函数,似乎万一 max
被执行,即使有多个对象比较到 0,它 returns 并列候选中的任意元素,无需进一步考虑。
对于这样的最大预期行为,是否有明显的技巧或函数,以便返回所有最大值?我在 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.
如果我理解的很好,你想要 Stream 中 max
值的频率。
实现此目的的一种方法是在从流中收集元素时将结果存储在 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)
收集它会给你一个 List [5, 5, 5]
。
本着相同精神的另一种方法是结合 counting()
收集器使用 group by 操作:
Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
TreeMap::new,
counting())).lastEntry(); //5=3 -> 5 appears 3 times
基本上你首先得到一个Map<Integer, List<Integer>>
。然后下游 counting()
收集器将 return 每个列表中由其键映射的元素数量,从而生成一个 Map。从那里您可以获取最大条目。
第一种方法需要存储流中的所有元素。第二个更好(请参阅 Holger 的评论),因为未构建中间 List
。在这两种方法中,结果都是在一次通过中计算出来的。
如果您从集合中获取来源,您可能需要使用 Collections.max
一次来查找最大值,然后使用 Collections.frequency
来查找该值出现了多少次。
它需要两次传递,但使用较少的内存,因为您不必构建数据结构。
等效的流是 coll.stream().max(...).get(...)
后跟 coll.stream().filter(...).count()
。
我不太确定你是否想
- (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
,然后再次迭代这些项目,将所有关系放入您选择的集合中。这将适用于 (a) 和 (b)。
我相信 OP 使用 Comparator
将输入划分为等价 classes,并且期望的结果是等价 class 的成员列表,即最大根据 Comparator
.
不幸的是,使用 int
值作为示例问题是一个糟糕的例子。所有相等的 int
值都是可替代的,因此没有保留等值值顺序的概念。也许更好的例子是使用字符串长度,其中所需的结果是 return 来自输入的字符串列表,这些字符串在该输入中都具有最长的长度。
如果不将至少部分结果存储在集合中,我不知道有什么方法可以做到这一点。
给定一个输入集合,说
List<String> list = ... ;
...这很简单,分两次完成,第一次获得最长的长度,第二次过滤具有该长度的字符串:
int longest = list.stream()
.mapToInt(String::length)
.max()
.orElse(-1);
List<String> result = list.stream()
.filter(s -> s.length() == longest)
.collect(toList());
如果输入是一个流,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;
}
});
}
这将中间结果存储在 ArrayList
中。不变量是任何此类列表中的所有元素在 Comparator
方面都是等效的。添加元素时,如果小于列表中的元素,则忽略;如果相等,则添加;如果它更大,则清空列表并添加新元素。合并也不是太困难:具有更大元素的列表是 returned,但如果它们的元素相等,则列表被附加。
给定一个输入流,这很容易使用:
Stream<String> input = ... ;
List<String> result = input.collect(maxList(comparing(String::length)));
我使用自定义下游收集器实现了更通用的收集器解决方案。可能有些读者会觉得有用:
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(","))));
}
我将按值分组并将值存储到 TreeMap
中以便对我的值进行排序,然后我将通过获取最后一个条目作为下一个来获得最大值:
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(groupingBy(Function.identity(), TreeMap::new, toList()))
.lastEntry()
.getValue()
.forEach(System.out::println);
输出:
5
5
如果您更愿意依赖库而不是此处的其他答案,StreamEx has a collector 可以做到这一点。
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(MoreCollectors.maxAll())
.forEach(System.out::println);
对于没有自然排序(即不实现 Comparable)的项目流,也有 a version which takes a Comparator。
System.out.println(
Stream.of(1, 3, 5, 3, 2, 3, 5)
.map(a->new Integer[]{a})
.reduce((a,b)->
a[0]==b[0]?
Stream.concat(Stream.of(a),Stream.of(b)).toArray() :
a[0]>b[0]? a:b
).get()
)
我一直在寻找关于这个问题的好答案,但有点复杂,在我自己弄清楚之前找不到任何东西,这就是为什么我发帖如果这对任何人有帮助。
我有一份小猫名单。 小猫是一个有名字、年龄和性别的对象。我必须 return 列出所有最小的小猫。
例如: 所以小猫列表将包含小猫对象 (k1, k2, k3, k4),它们的年龄相应地为 (1, 2, 3, 1)。我们要return[k1,k4],因为他们都是最小的。如果只有一个最小的存在,函数应该 return [k1(youngest)].
查找列表的最小值(如果存在):
Optional<Kitten> minKitten = kittens.stream().min(Comparator.comparingInt(Kitten::getAge));
按最小值筛选列表
return minKitten.map(value -> kittens.stream().filter(kitten -> kitten.getAge() == value.getAge()) .collect(Collectors.toList())).orElse(Collections.emptyList());
以下两行将在不实现单独的比较器的情况下完成:
List<Integer> list = List.of(1, 3, 5, 3, 2, 3, 5);
list.stream().filter(i -> i == (list.stream().max(Comparator.comparingInt(i2 -> i2))).get()).forEach(System.out::println);