如何删除所有与特定条件匹配的元素,除了最大的元素与流API

发布于 2025-01-25 23:52:50 字数 721 浏览 4 评论 0原文

我的问题是:有没有更好的方法来实施此任务?

我有一个有序元素的列表(在此示例中,age,最小的第一个)。 我想删除满足条件的所有元素(在此示例 red elements 中),但请保留它们的第一个2

Stream<ElementsVO> stream = allElements.stream();
Stream<ElementsVO> redStream = stream.filter(elem->elem.getColor()==RED).sorted((c1, c2) -> { return c1.getAge() - c2.getAge();
            }).limit(2);
Stream<ElementsVO> nonRedStream=stream.filter(elem->elem.getColor()!=RED);

List<ElementsVO> resultList = Stream.concat(redStream,nonRedStream).sorted((c1, c2) -> { return c1.getAge() - c2.getAge();
            }).collect(Collectors.toList());
    

有什么想法可以改善这一点吗?有什么方法可以通过流实现累加器功能或类似的方法?

My question is: is there a better way to implement this task?

I have a list of orderable elements (in this example by age, the youngest first).
And I want to delete all elements that fulfill a condition (in this example red elements) but keep the first 2 of them.

Stream<ElementsVO> stream = allElements.stream();
Stream<ElementsVO> redStream = stream.filter(elem->elem.getColor()==RED).sorted((c1, c2) -> { return c1.getAge() - c2.getAge();
            }).limit(2);
Stream<ElementsVO> nonRedStream=stream.filter(elem->elem.getColor()!=RED);

List<ElementsVO> resultList = Stream.concat(redStream,nonRedStream).sorted((c1, c2) -> { return c1.getAge() - c2.getAge();
            }).collect(Collectors.toList());
    

Any idea to improve this? Any way to implement an accumulator function or something like that with streams?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

兮子 2025-02-01 23:52:51

从技术上讲,您可以使用一个陈述的谓词来完成此操作:

Predicate<ElementsV0> statefulPredicate = new Predicate<ElementsV0>() {
  private int reds = 0;

  @Override public boolean test(ElementsV0 e) {
    if (elem.getColor() == RED) {
      reds++;
      return reds < 2;
    }
    return true;
  }
};

然后:

List<ElementsVO> resultList =
    allElements.stream()
        .sorted(comparingInt(ElementsV0::getAge))
        .filter(statefulPredicate)
        .collect(toList());

可能会工作,但这是对流API的违反: stream。 Filter 说谓词应该是无状态的,通常允许流实施以任何顺序应用过滤器。对于小输入列表,顺序流式流,这几乎可以肯定是列表中的外观顺序,但不能保证。

警告。您当前的方式有效,尽管您可以使用collectors.partitioningby避免对其进行两次迭代。

You can technically do this with a stateful predicate:

Predicate<ElementsV0> statefulPredicate = new Predicate<ElementsV0>() {
  private int reds = 0;

  @Override public boolean test(ElementsV0 e) {
    if (elem.getColor() == RED) {
      reds++;
      return reds < 2;
    }
    return true;
  }
};

Then:

List<ElementsVO> resultList =
    allElements.stream()
        .sorted(comparingInt(ElementsV0::getAge))
        .filter(statefulPredicate)
        .collect(toList());

This might work, but it is a violation of the Stream API: the documentation for Stream.filter says that the predicate should be stateless, which in general allows the stream implementation to apply the filter in any order. For small input lists, streamed sequentially, this will almost certainly be the appearance order in the list, but it's not guaranteed.

Caveat emptor. Your current way works, although you could do the partitioning of the list more efficiently using Collectors.partitioningBy to avoid iterating it twice.

哆兒滾 2025-02-01 23:52:51

您可以实现自定义收集器,该将维护两个单独的RED 和 non-Red 元素的单独集合。

而且,由于您只需要两个红色元素即可提高性能,因此您可以引入部分分类。 IE的非红色元素的收集需要维护订单,并且最多必须是大小2,而与具有属性属性的元素的分类相比,排序的开销将远不那么重要。代码>红色仅选择其中两个。

为了创建自定义收集器,您可以使用静态方法 collector.of() ,期望以下参数:

  • 供应商 <代码>供应商&lt; a&gt; 旨在提供 Mutable容器 哪个存储流的元素。因为我们需要将颜色元素分为两个组作为容器,所以我们可以使用仅包含2 的地图(truefalse),表示映射到此的元素是 red 。为了存储 red elements 并执行部分分类,我们需要一个能够维护订单的集合。 PriorityQueue是为此目的的一个不错的选择。为了存储所有其他元素,我使用了arraydeque,它不维护顺序,并且像arraylist一样快。
  • 累加器 Biconsumer&lt; a,t&gt;定义如何将元素添加到由供应商提供的 Mutable容器中。对于此任务,累加器需要保证包含 red elements 的队列不会通过拒绝小于先前添加到的最低值的值来超过给定的大小如果大小已达到限制并且需要添加新值,则队列并删除最低值。此功能提取到单独的方法tryadd()
  • combiner 二进制处理器&lt; a&gt; a&gt; Combiner()建立了一个规则,即如何合并并行执行流时获得的两个容器。在这里,组合仪依靠与累加器描述的相同逻辑。
  • 终结器 函数&lt; a,r&gt;旨在通过转换可突变容器来产生最终结果。在下面的代码中,终结器将两个队列的内容都转储到流中,对它们进行分类并收集到不可变的列表中。
  • 特征允许通过提供有关其应如何运作的其他信息来微调收集器。在这里,正在应用一个特征collector.characteristics.unordered。这表明并行产生的部分结果的部分结果的顺序不显着,可以通过平行流来改善该收集器的性能。

该代码可能看起来像这样:

public static void main(String[] args) {
    List<ElementsVO> allElements =
        List.of(new ElementsVO(Color.RED, 25), new ElementsVO(Color.RED, 23), new ElementsVO(Color.RED, 27),
                new ElementsVO(Color.BLACK, 19), new ElementsVO(Color.GREEN, 23), new ElementsVO(Color.GREEN, 29));
    
    Comparator<ElementsVO> byAge = Comparator.comparing(ElementsVO::getAge);
    
    List<ElementsVO> resultList = allElements.stream()
        .collect(getNFiltered(byAge, element -> element.getColor() != Color.RED, 2));

    resultList.forEach(System.out::println);
}

下面的方法负责创建基于给定谓词的元素的收集器,并将按照提供的比较器对它们进行排序。 。

public static <T> Collector<T, ?, List<T>> getNFiltered(Comparator<T> comparator,
                                                        Predicate<T> condition,
                                                        int limit) {
    return Collector.of(
        () -> Map.of(true, new PriorityQueue<>(comparator),
            false, new ArrayDeque<>()),
        (Map<Boolean, Queue<T>> isRed, T next) -> {
            if (condition.test(next)) isRed.get(false).add(next);
            else tryAdd(isRed.get(true), next, comparator, limit);
        },
        (Map<Boolean, Queue<T>> left, Map<Boolean, Queue<T>> right) -> {
            left.get(false).addAll(right.get(false));
            left.get(true).forEach(next -> tryAdd(left.get(true), next, comparator, limit));
            return left;
        },
        (Map<Boolean, Queue<T>> isRed) -> isRed.values().stream()
            .flatMap(Queue::stream).sorted(comparator).toList(),
        Collector.Characteristics.UNORDERED
    );
}

此方法负责将下一个红元素添加到优先级队列中。它希望A 比较器为了确定是否应添加或丢弃下一个元素,以及队列的最大大小的值(2)检查是否超过。

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 the next element 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);
}

输出

lementsVO{color=BLACK, age=19}
ElementsVO{color=GREEN, age=23}
ElementsVO{color=RED, age=25}
ElementsVO{color=RED, age=27}
ElementsVO{color=GREEN, age=29}

You can implement a custom collector that will maintain two separate collections of RED and non-RED element.

And since you need only two red elements having the greatest age to improve performance, you can introduce a partial sorting. I.e. collection of non-red element needs to maintain an order and always must be of size 2 at most, with that overhead of sorting will be far less significant in comparison to sorting of elements having the property of RED in order to pick only two of them.

In order to create a custom collector, you might make use of the static method Collector.of() which expects the following arguments:

  • Supplier Supplier<A> is meant to provide a mutable container which store elements of the stream. Because we need to separate elements by color into two groups as a container, we can use a map that will contain only 2 keys (true and false), denoting whether elements mapped to this key are red. In order to store red-elements and perform a partial sorting, we need a collection that is capable of maintaining the order. PriorityQueue is a good choice for that purpose. To store all other elements, I've used ArrayDeque, which doesn't maintain the order and as fast as ArrayList.
  • Accumulator BiConsumer<A,T> defines how to add elements into the mutable container provided by the supplier. For this task, the accumulator needs to guarantee that the queue, containing red-elements will not exceed the given size by rejecting values that are smaller than the lowest value previously added to the queue and by removing the lowest value if the size has reached the limit and a new value needs to be added. This functionality extracted into a separate method tryAdd()
  • Combiner BinaryOperator<A> combiner() establishes a rule on how to merge two containers obtained while executing stream in parallel. Here, combiner rely on the same logic that was described for accumulator.
  • Finisher Function<A,R> is meant to produce the final result by transforming the mutable container. In the code below, finisher dumps the contents of both queues into a stream, sorts them and collects into an immutable list.
  • Characteristics allow fine-tuning the collector by providing additional information on how it should function. Here a characteristic Collector.Characteristics.UNORDERED is being applied. Which indicates that the order in which partial results of the reduction produced in parallel is not significant, that can improve performance of this collector with parallel streams.

The code might look like this:

public static void main(String[] args) {
    List<ElementsVO> allElements =
        List.of(new ElementsVO(Color.RED, 25), new ElementsVO(Color.RED, 23), new ElementsVO(Color.RED, 27),
                new ElementsVO(Color.BLACK, 19), new ElementsVO(Color.GREEN, 23), new ElementsVO(Color.GREEN, 29));
    
    Comparator<ElementsVO> byAge = Comparator.comparing(ElementsVO::getAge);
    
    List<ElementsVO> resultList = allElements.stream()
        .collect(getNFiltered(byAge, element -> element.getColor() != Color.RED, 2));

    resultList.forEach(System.out::println);
}

The method below is responsible for creating of a collector that partition the elements based on the given predicate and will sort them in accordance with the provided comparator.

public static <T> Collector<T, ?, List<T>> getNFiltered(Comparator<T> comparator,
                                                        Predicate<T> condition,
                                                        int limit) {
    return Collector.of(
        () -> Map.of(true, new PriorityQueue<>(comparator),
            false, new ArrayDeque<>()),
        (Map<Boolean, Queue<T>> isRed, T next) -> {
            if (condition.test(next)) isRed.get(false).add(next);
            else tryAdd(isRed.get(true), next, comparator, limit);
        },
        (Map<Boolean, Queue<T>> left, Map<Boolean, Queue<T>> right) -> {
            left.get(false).addAll(right.get(false));
            left.get(true).forEach(next -> tryAdd(left.get(true), next, comparator, limit));
            return left;
        },
        (Map<Boolean, Queue<T>> isRed) -> isRed.values().stream()
            .flatMap(Queue::stream).sorted(comparator).toList(),
        Collector.Characteristics.UNORDERED
    );
}

This method is responsible for adding the next red-element into the priority queue. It expects a comparator in order to be able to determine whether the next element should be added or discarded, and a value of the maximum size of the queue (2), to check if it was exceeded.

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 the next element 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);
}

Output

lementsVO{color=BLACK, age=19}
ElementsVO{color=GREEN, age=23}
ElementsVO{color=RED, age=25}
ElementsVO{color=RED, age=27}
ElementsVO{color=GREEN, age=29}
眉目亦如画i 2025-02-01 23:52:51

我写了一个带有谓词的通用收集器和一个要添加的元素的限制,与谓词相匹配:

public class LimitedMatchCollector<T> implements Collector<T, List<T>, List<T>> {

    private Predicate<T> filter;

    private int limit;

    public LimitedMatchCollector(Predicate<T> filter, int limit)
    {
        super();
        this.filter = filter;
        this.limit = limit;
    }

    private int count = 0;

    @Override
    public Supplier<List<T>> supplier() {
        return () -> new ArrayList<T>();
    }

    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return this::accumulator;
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return this::combiner;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Stream.of(Characteristics.IDENTITY_FINISH)
                .collect(Collectors.toCollection(HashSet::new));
    }

    public List<T> accumulator(List<T> list , T e) {
        if (filter.test(e)) {

           if (count >= limit) {
               return list;
           }
           count++;
        }
        list.add(e);
        return list;
    }

    public List<T> combiner(List<T> left , List<T> right) {
        right.forEach( e -> {
            if (filter.test(e)) {

                if (count < limit) {
                    left.add(e);
                    count++;
                }
             }
        });

        return left;
    }

    @Override
    public Function<List<T>, List<T>> finisher()
    {
        return Function.identity();
    }

}

用法:

    List<ElementsVO> list = Arrays.asList(new ElementsVO("BLUE", 1)
           ,new ElementsVO("BLUE", 2) // made color a String
           ,new ElementsVO("RED", 3)
           ,new ElementsVO("RED", 4)
           ,new ElementsVO("GREEN", 5)
           ,new ElementsVO("RED", 6)
           ,new ElementsVO("YELLOW", 7)
           );
    System.out.println(list.stream().collect(new LimitedMatchCollector<ElementsVO>( (e) -> "RED".equals(e.getColor()),2)));

I wrote a generic Collector with a predicate and a limit of elements to add which match the predicate:

public class LimitedMatchCollector<T> implements Collector<T, List<T>, List<T>> {

    private Predicate<T> filter;

    private int limit;

    public LimitedMatchCollector(Predicate<T> filter, int limit)
    {
        super();
        this.filter = filter;
        this.limit = limit;
    }

    private int count = 0;

    @Override
    public Supplier<List<T>> supplier() {
        return () -> new ArrayList<T>();
    }

    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return this::accumulator;
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return this::combiner;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Stream.of(Characteristics.IDENTITY_FINISH)
                .collect(Collectors.toCollection(HashSet::new));
    }

    public List<T> accumulator(List<T> list , T e) {
        if (filter.test(e)) {

           if (count >= limit) {
               return list;
           }
           count++;
        }
        list.add(e);
        return list;
    }

    public List<T> combiner(List<T> left , List<T> right) {
        right.forEach( e -> {
            if (filter.test(e)) {

                if (count < limit) {
                    left.add(e);
                    count++;
                }
             }
        });

        return left;
    }

    @Override
    public Function<List<T>, List<T>> finisher()
    {
        return Function.identity();
    }

}

Usage:

    List<ElementsVO> list = Arrays.asList(new ElementsVO("BLUE", 1)
           ,new ElementsVO("BLUE", 2) // made color a String
           ,new ElementsVO("RED", 3)
           ,new ElementsVO("RED", 4)
           ,new ElementsVO("GREEN", 5)
           ,new ElementsVO("RED", 6)
           ,new ElementsVO("YELLOW", 7)
           );
    System.out.println(list.stream().collect(new LimitedMatchCollector<ElementsVO>( (e) -> "RED".equals(e.getColor()),2)));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文