如何使用 Stream API 从列表中获取具有最高值的 3 个对象

How to fetch 3 objects having the highest values from a List with Stream API

我有这样的方法:

public String mostExpensiveItems() {
        List<Entry> myList = getList();
        List<Double> expensive = myList.stream()
                .map(Entry::getAmount)
                .sorted(Comparator.reverseOrder())
                .limit(3)
                .toList();

        return "";
    }

此方法需要 return 3 最昂贵商品的 产品 ID 作为字符串,如下所示:

"item1, item2, item3"

我应该只能使用流,但我卡在这里了。我应该能够按价值对项目进行排序,然后获取产品 ID,但我似乎无法让它工作。

条目class

public class Entry {

    private String productId;
    private LocalDate date;
    private String state;
    private String category;
    private Double amount;

    public Entry(LocalDate orderDate, String state, String productId, String category, Double sales) {
        this.date = orderDate;
        this.productId = productId;
        this.state = state;
        this.category = category;
        this.amount = sales;
    }

    public String getProductId() {
        return productId;
    }

假设产品ID在Entry里面,可以是这样的。

public String mostExpensiveItems() {
    List<Entry> myList = getList();
    List<String> expensive = myList.stream()
            .sorted(Comparator.comparing(Entry::getAmount).reversed())
            .limit(3)
            .map(Entry::getProductID)
            .toList();

    return "";
}

注意:我还没有测试这个,但是这个应该可以传达这个想法。

您不需要为此任务对所有给定数据进行排序。因为排序是多余的,当只需要获取3个最大值时。

因为对孔数据集进行排序会花费O(n log n)时间。同时,此任务可以在单次遍历列表中完成,只维护 3 个最大的先前遇到的排序顺序的值。并且时间复杂度将非常接近线性时间

要使用流实现部分排序,您可以定义一个自定义收集器(一个负责从流中收集数据的对象)。

您可以创建一个 自定义收集器 通过使用静态方法 Collector.of() 的一个版本内联或通过创建一个 class 来实现Collector界面。

这些是您在定义自定义收集器时需要提供的参数:

  • Supplier Supplier<A> 旨在提供一个 可变容器 来存储流的元素。在这种情况下,因为我们需要执行 部分排序 PriorityQueue 作为可变容器将很方便。
  • Accumulator BiConsumer 定义如何将元素添加到供应商提供的容器。对于此任务,累加器 需要通过拒绝小于先前添加到队列中的最低值的值并通过删除最低值来保证队列不会超过给定大小,如果大小已达到需要添加新值的限制​​。
  • Combiner BinaryOperator<A> combiner() 建立了如何合并并行执行流时获得的两个 容器 的规则。这里的组合器依赖于为累加器描述的相同逻辑。
  • Finisher Function<A,R> 旨在通过转换可变容器来产生最终结果。下面代码中的 finisher 函数将队列变成一个不可变列表。
  • Characteristics 允许提供额外的信息,例如 Collector.Characteristics.UNORDERED 在这种情况下使用的表示在执行时产生的部分归约结果的顺序parallel 并不重要,这可以提高这个收集器与并行流的性能。

请注意,对于 Collector.of(),只有 供应商累加器组合器强制性的,如果需要定义其他参数。

如果我们对其应用泛型类型参数并声明它期望比较器[=85=,则下面生成收集器的方法将更可重用] 作为参数(将在 PriorityQueue 的构造函数中使用,并在向队列添加元素时使用)。

自定义收集器:

public static <T> Collector<T, ?, List<T>> getMaxN(int size, Comparator<T> comparator) {
    
    return Collector.of(
        () -> new PriorityQueue<>(comparator),
        (Queue<T> queue, T next) -> tryAdd(queue, next, comparator, size),
        (Queue<T> left, Queue<T> right) -> {
            right.forEach(next -> tryAdd(left, next, comparator, size));
            return left;
        },
        (Queue<T> queue) -> queue.stream().toList(),
        Collector.Characteristics.UNORDERED);
}

public static <T> void tryAdd(Queue<T> queue, T next, Comparator<T> comparator, int size) {
    if (queue.size() == size && comparator.compare(queue.element(), next) < 0) queue.remove(); // if next value is greater than the smallest element in the queue and max size has been exceeded the smallest element needs to be removed from the queue
    if (queue.size() < size) queue.add(next);
}

流:

public static <T> String getMostExpensive(List<T> list, Function<T, String> function,
                                          Comparator<T> comparator, int limit) {
    
    return list.stream()
        .collect(getMaxN(limit, comparator))
        .stream()
        .map(function)
        .collect(Collectors.joining(", "));
}

main() - 带有虚拟 Entries 的演示只需要 amount 作为参数。

public static void main(String[] args) {
    List<Entry> entries =
        List.of(new Entry("item1", 2.6), new Entry("item2", 3.5), new Entry("item3", 5.7),
                new Entry("item4", 1.9), new Entry("item5", 3.2), new Entry("item6", 9.5),
                new Entry("item7", 7.2), new Entry("item8", 8.1), new Entry("item9", 7.9));

    System.out.println(getMostExpensive(entries, Entry::getProductId,
                                        Comparator.comparingDouble(Entry::getAmount), 3));
}

输出

[item9, item6, item8] // final result is not sorted PriorityQueue maintains the elements in unsorted order (sorting happens only while dequeue operation happens), if these values are requeted to be sorted it could be done by changing the finisher function