Java Streams -如何按值分组并找到每个组的最小值和最大值?

hgqdbh6s  于 2023-04-10  发布在  Java
关注(0)|答案(4)|浏览(255)

举个例子,有一个汽车对象,并根据模型(group by)找到了最小和最大价格值。

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

但是我找不到哪些汽车对象有最大和最小价格。我该怎么做呢?

y4ekin9u

y4ekin9u1#

如果你只对每组中的一个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)可能更有效。

fiei3ece

fiei3ece2#

这里有一个非常简洁的解决方案。它将所有的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都被收集,而不仅仅是当前的最小值和最大值。但是除非数据集非常大,否则我不认为它会被注意到。

mkh04yzy

mkh04yzy3#

我想提出一个解决方案,(在我看来)争取最大的可读性(这减少了例如,这种代码的维护负担)。
它是基于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()));
dohp0rv5

dohp0rv54#

**对于汽车及其最高价格:**Map〈String,Optional〉groupByMaxPrice =carsDetails.stream().collect(Collectors.groupingBy(Car::getMake,Collectors.maxBy(Comparator.comparing(Car::getPrice)));
**对于汽车及其最低价格:**Map〈String,Optional〉groupByMaxPrice =carsDetails.stream().collect(Collectors.groupingBy(Car::getMake,Collectors.maxBy(Comparator.comparing(Car::getPrice)));

相关问题