例如,我有一个汽车对象,并根据模型(分组依据)发现了最小和最大价格值。
List<Car> carsDetails = UserDB.getCarsDetails();
Map<String, DoubleSummaryStatistics> collect4 = carsDetails.stream()
.collect(Collectors.groupingBy(Car::getMake, Collectors.summarizingDouble(Car::getPrice)));
collect4.entrySet().forEach(e->System.out.println(e.getKey()+" "+e.getValue().getMax()+" "+e.getValue().getMin()));
output :
Lexus 94837.79 17569.59
Subaru 96583.25 8498.41
Chevrolet 99892.59 6861.85
但是我找不到哪个汽车对象具有最高和最低价格。我该怎么办?
答案 0 :(得分:7)
如果您对每个组只感兴趣一个Car
,则可以使用例如
Map<String, Car> mostExpensives = carsDetails.stream()
.collect(Collectors.toMap(Car::getMake, Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(Car::getPrice))));
mostExpensives.forEach((make,car) -> System.out.println(make+" "+car));
但是由于您想要最昂贵和最便宜的产品,因此需要这样的东西:
Map<String, List<Car>> mostExpensivesAndCheapest = carsDetails.stream()
.collect(Collectors.toMap(Car::getMake, car -> Arrays.asList(car, car),
(l1,l2) -> Arrays.asList(
(l1.get(0).getPrice()>l2.get(0).getPrice()? l2: l1).get(0),
(l1.get(1).getPrice()<l2.get(1).getPrice()? l2: l1).get(1))));
mostExpensivesAndCheapest.forEach((make,cars) -> System.out.println(make
+" cheapest: "+cars.get(0)+" most expensive: "+cars.get(1)));
由于没有与DoubleSummaryStatistics
等效的通用统计对象,因此该解决方案带来了一些不便。如果这种情况不止一次发生,那么值得用这样的课程填补这一空白:
/**
* Like {@code DoubleSummaryStatistics}, {@code IntSummaryStatistics}, and
* {@code LongSummaryStatistics}, but for an arbitrary type {@code T}.
*/
public class SummaryStatistics<T> implements Consumer<T> {
/**
* Collect to a {@code SummaryStatistics} for natural order.
*/
public static <T extends Comparable<? super T>> Collector<T,?,SummaryStatistics<T>>
statistics() {
return statistics(Comparator.<T>naturalOrder());
}
/**
* Collect to a {@code SummaryStatistics} using the specified comparator.
*/
public static <T> Collector<T,?,SummaryStatistics<T>>
statistics(Comparator<T> comparator) {
Objects.requireNonNull(comparator);
return Collector.of(() -> new SummaryStatistics<>(comparator),
SummaryStatistics::accept, SummaryStatistics::merge);
}
private final Comparator<T> c;
private T min, max;
private long count;
public SummaryStatistics(Comparator<T> comparator) {
c = Objects.requireNonNull(comparator);
}
public void accept(T t) {
if(count == 0) {
count = 1;
min = t;
max = t;
}
else {
if(c.compare(min, t) > 0) min = t;
if(c.compare(max, t) < 0) max = t;
count++;
}
}
public SummaryStatistics<T> merge(SummaryStatistics<T> s) {
if(s.count > 0) {
if(count == 0) {
count = s.count;
min = s.min;
max = s.max;
}
else {
if(c.compare(min, s.min) > 0) min = s.min;
if(c.compare(max, s.max) < 0) max = s.max;
count += s.count;
}
}
return this;
}
public long getCount() {
return count;
}
public T getMin() {
return min;
}
public T getMax() {
return max;
}
@Override
public String toString() {
return count == 0? "empty": (count+" elements between "+min+" and "+max);
}
}
将其添加到代码库后,您可以像使用它
Map<String, SummaryStatistics<Car>> mostExpensives = carsDetails.stream()
.collect(Collectors.groupingBy(Car::getMake,
SummaryStatistics.statistics(Comparator.comparing(Car::getPrice))));
mostExpensives.forEach((make,cars) -> System.out.println(make+": "+cars));
如果getPrice
返回double
,则使用Comparator.comparingDouble(Car::getPrice)
代替Comparator.comparing(Car::getPrice)
可能更有效。
答案 1 :(得分:1)
我想提出一种解决方案,以达到最大的可读性(例如,减轻此类代码的维护负担)。
它基于Collector
,因此(作为奖励)可以与并行Stream
一起使用。假定对象不是空的。
final class MinMaxFinder<T> {
private final Comparator<T> comparator;
MinMaxFinder(Comparator<T> comparator) {
this.comparator = comparator;
}
Collector<T, ?, MinMaxResult<T>> collector() {
return Collector.of(
MinMaxAccumulator::new,
MinMaxAccumulator::add,
MinMaxAccumulator::combine,
MinMaxAccumulator::toResult
);
}
private class MinMaxAccumulator {
T min = null;
T max = null;
MinMaxAccumulator() {
}
private boolean isEmpty() {
return min == null;
}
void add(T item) {
if (isEmpty()) {
min = max = item;
} else {
updateMin(item);
updateMax(item);
}
}
MinMaxAccumulator combine(MinMaxAccumulator otherAcc) {
if (isEmpty()) {
return otherAcc;
}
if (!otherAcc.isEmpty()) {
updateMin(otherAcc.min);
updateMax(otherAcc.max);
}
return this;
}
private void updateMin(T item) {
min = BinaryOperator.minBy(comparator).apply(min, item);
}
private void updateMax(T item) {
max = BinaryOperator.maxBy(comparator).apply(max, item);
}
MinMaxResult<T> toResult() {
return new MinMaxResult<>(min, max);
}
}
}
类似于结果持有人的价值类:
public class MinMaxResult<T> {
private final T min;
private final T max;
public MinMaxResult(T min, T max) {
this.min = min;
this.max = max;
}
public T min() {
return min;
}
public T max() {
return max;
}
}
用法:
MinMaxFinder<Car> minMaxFinder = new MinMaxFinder<>(Comparator.comparing(Car::getPrice));
Map<String, MinMaxResult<Car>> minMaxResultMap = carsDetails.stream()
.collect(Collectors.groupingBy(Car::getMake, minMaxFinder.collector()));
答案 2 :(得分:1)
这是一个非常简洁的解决方案。它将所有Car
收集到SortedSet
中,因此无需任何其他类即可工作。
Map<String, SortedSet<Car>> grouped = carDetails.stream()
.collect(groupingBy(Car::getMake, toCollection(
() -> new TreeSet<>(comparingDouble(Car::getPrice)))));
grouped.forEach((make, cars) -> System.out.println(make
+ " cheapest: " + cars.first()
+ " most expensive: " + cars.last()));
可能的缺点是性能,因为收集了所有Car
,而不仅仅是当前的最小值和最大值。但是除非数据集很大,否则我认为它不会很明显。