Java 8 - Stream - 按值分组并查找该对象的最小值和最大值

Lea*_*oop 5 java java-8 java-stream

对于我的例子,有汽车对象,并发现基于模型的最小和最大价格值(分组依据).

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
Run Code Online (Sandbox Code Playgroud)

但我找不到哪个车对象有最高和最低价格.我怎样才能做到这一点?

Hol*_*ger 10

如果您对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));
Run Code Online (Sandbox Code Playgroud)

但既然你想要最便宜和最便宜的,你需要这样的东西:

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)));
Run Code Online (Sandbox Code Playgroud)

由于没有相当于的通用统计对象,因此该解决方案带来一些不便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);
    }
}
Run Code Online (Sandbox Code Playgroud)

将此添加到您的代码库后,您可以使用它

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));
Run Code Online (Sandbox Code Playgroud)

如果getPrice返回double,则使用Comparator.comparingDouble(Car::getPrice)而不是更有效Comparator.comparing(Car::getPrice).

  • 可能有点挑剔.. --&gt; `Comparator.comparingDouble(...)` 而不是 `Comparator.comparing(..)`。我第一次从霍尔格那里学到了这个:) (2认同)
  • @Aominè 当我确定`getPrice` 返回`double` 而不是`Double` 时,我会使用它。 (2认同)
  • @LearnHadoop 好,如上所述,逻辑应该类似于数值统计对象,例如 [`DoubleSummaryStatistics`](https://docs.oracle.com/javase/8/docs/api/java/util/ DoubleSummaryStatistics.html),因此您可以在他们的文档中找到一些提示。不是数字,我们没有 sum 和 average,所以它只维护 `min`、`max` 和 `count`。基本上,收集器会为每个元素分别调用“accept”。当与`groupingBy`结合时,对于组的每个元素。只有在并行计算时,才可能调用 `merge` 方法来合并部分结果。 (2认同)

rol*_*lve 7

这是一个非常简洁的解决方案。它将所有Cars 收集到 a 中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()));
Run Code Online (Sandbox Code Playgroud)

一个可能的缺点是性能,因为所有的Car数据都会被收集,而不仅仅是当前的最小值和最大值。但除非数据集非常大,否则我认为不会引人注目。