是否有一种简洁的方法可以一次性提取流的最小值和最大值(基于某些比较器)?
似乎有很多方法可以单独获取最小值和最大值,或者我可以将流排序到临时对象中,例如:
List<T> sorted = Stream.of(...).sorted().collect(Collectors.toList());
T min = sorted.get(0);
T max = sorted.get(sorted.size() - 1);
但这并不简洁,需要分配一个临时对象。我不想分配临时对象或通过流进行两次传递。有替代方案吗?
Pair<T> extent = Stream.of(...).???
如果您有整数流,
summarizingInt
收集器就可以很好地工作。
IntSummaryStatistics stats = Stream.of(2,4,3,2)
.collect(Collectors.summarizingInt(Integer::intValue));
int min = stats.getMin();
int max = stats.getMax();
如果你有双打,你可以使用
summarizingDouble
收集器。
DoubleSummaryStatistics stats2 = Stream.of(2.4, 4.3, 3.3, 2.5)
.collect(Collectors.summarizingDouble((Double::doubleValue)));
如果这是经常需要的功能,我们最好制作一个
Collector
来完成这项工作。我们需要一个 Stats
类来保存 count, min, max
,并需要工厂方法来创建统计收集器。
Stats<String> stats = stringStream.collect(Stats.collector())
fooStream.collect(Stats.collector(fooComparator))
(也许更好的便捷方法是
Stats.collect(stream)
)
我做了一个例子
Stats
类 -
https://gist.github.com/zhong-j-yu/ac5028573c986f7820b25ea2e74ed672
public class Stats<T>
{
int count;
final Comparator<? super T> comparator;
T min;
T max;
public Stats(Comparator<? super T> comparator)
{
this.comparator = comparator;
}
public int count(){ return count; }
public T min(){ return min; }
public T max(){ return max; }
public void accept(T val)
{
if(count==0)
min = max = val;
else if(comparator.compare(val, min)<0)
min = val;
else if(comparator.compare(val, max)>0)
max = val;
count++;
}
public Stats<T> combine(Stats<T> that)
{
if(this.count==0) return that;
if(that.count==0) return this;
this.count += that.count;
if(comparator.compare(that.min, this.min)<0)
this.min = that.min;
if(comparator.compare(that.max, this.max)>0)
this.max = that.max;
return this;
}
public static <T> Collector<T, Stats<T>, Stats<T>> collector(Comparator<? super T> comparator)
{
return Collector.of(
()->new Stats<>(comparator),
Stats::accept,
Stats::combine,
Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH
);
}
public static <T extends Comparable<? super T>> Collector<T, Stats<T>, Stats<T>> collector()
{
return collector(Comparator.naturalOrder());
}
}
将流的每个元素映射到一对,其中两个元素代表最小值和最大值;然后通过取最小值中的最小值和最大值中的最大值来减少对。
例如,使用一些
Pair
类和一些 Comparator<T>
:
Comparator<T> comparator = ...;
Optional<Pair<T, T>> minMax = list.stream()
.map(i -> Pair.of(i /* "min" */, i /* "max" */))
.reduce((a, b) -> Pair.of(
// The min of the min elements.
comparator.compare(a.first, b.first) < 0 ? a.first : b.first,
// The max of the max elements.
comparator.compare(a.second, b.second) > 0 ? a.second : b.second));
从 Java 12 开始,您可以使用
Collectors::teeing
: 在一次传递中获得两个或更多结果
class Movie {
String title;
double rating;
//...
}
class Pair<T1, T2> {
T1 left;
T2 right;
//...
}
@Test
void shouldFindWorstAndBestMovie() {
var m1 = new Movie("Groundhog Day", 8);
var m2 = new Movie("Stop! Or My Mom Will Shoot", 4.4);
var m3 = new Movie("Forrest Gump", 8.8);
var ratingComparator = Comparator.comparing(Movie::getRating);
Pair<Movie, Movie> result = Stream.of(m1, m2, m3)
.collect(Collectors.teeing(
Collectors.minBy(ratingComparator),
Collectors.maxBy(ratingComparator),
(min, max) -> new Pair<>(min.orElse(null), max.orElse(null))
));
assertEquals(m2, result.getLeft(), "min does not match");
assertEquals(m3, result.getRight(), "max does not match");
}
您可以在本文中找到更多详细信息和示例。
我想你需要这个
IntStream myIntStream = IntStream.rangeClosed(1, 100);
IntSummaryStatistics intStatistic = myIntStream.summaryStatistics();
System.out.println("Max: " + intStatistic.getMax() + " Min: " + intStatistic.getMin());
对于相当简洁的纯 Java 解决方案,您可以使用 .peek()。这并不是真正的函数式,因为 .peek() 所做的任何事情都是副作用。但这确实一次性完成了所有工作,不需要排序,也不太冗长。有一个“临时”对象,即 AtomicRef,但您可能会分配一个本地 var/ref 来保存最小值和最大值。
Comparator<T> cmp = ...
Stream<T> source = ...
final AtomicReference<T> min = new AtomicReference<T>();
Optional<T> max = source.peek(t -> {if (cmp.compare(t,min.get()) < 0) min.set(t);})
.max(cmp);
//Do whatever with min.get() and max.get()
使用任何可变
Pair
类的简单方法:
final Pair<T, T> pair = new Pair<>();
final Comparator<T> comparator = ...;
Stream.of(...).forEachOrdered(e -> {
if(pair.first == null || comparator.compare(e, pair.first) < 0){
pair.first = e;
}
if(pair.second == null || comparator.compare(e, pair.second) > 0){
pair.second = e;
}
});
列表列表 = Arrays.asList(1,5,8,3,6);
System.out.println(list.stream().count());//计数
System.out.println(list.stream().min((i1,i2) -> i1.compareTo(i2)).stream() .findFirst().get()); //最小元素
System.out.println(list.stream().max((i1,i2) -> i1.compareTo(i2)).stream() .findFirst().get()); // 最大元素