从 Java 8 流中取出每个第 n 个元素 [英] Take every nth element from a Java 8 stream

查看:19
本文介绍了从 Java 8 流中取出每个第 n 个元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个这样的列表:

Suppose I have a list like this :

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

是否可以使用 Java 8 流从该列表中获取每个第二个元素以获得以下内容?

Is it possible to use a Java 8 stream to take every second element from this list to obtain the following?

[1, 3, 5, 7, 9]

或者甚至每三个元素?

[1, 4, 7, 10]

基本上,我正在寻找一个函数来获取流的每个第 n 个元素:

Basically, I'm looking for a function to take every nth element of a stream:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list2 = list.stream().takenth(3).collect(Collectors.toList());
System.out.println(list2);
// => [1, 4, 7, 10]

推荐答案

引入 Java 流的主要动机之一是允许并行操作.这导致要求对 Java 流(例如 mapfilter)的操作独立于流中项目或其周围项目的位置.这样做的优点是可以轻松拆分流以进行并行处理.它的缺点是使某些操作更加复杂.

One of the prime motivations for the introduction of Java streams was to allow parallel operations. This led to a requirement that operations on Java streams such as map and filter be independent of the position of the item in the stream or the items around it. This has the advantage of making it easy to split streams for parallel processing. It has the disadvantage of making certain operations more complex.

所以简单的答案是,没有简单的方法来完成诸如获取每第 n 个项目或将每个项目映射到所有先前项目的总和之类的事情.

So the simple answer is that there is no easy way to do things such as take every nth item or map each item to the sum of all previous items.

实现您的要求的最直接方法是使用您从中流式传输的列表的索引:

The most straightforward way to implement your requirement is to use the index of the list you are streaming from:

List<String> list = ...;
return IntStream.range(0, list.size())
    .filter(n -> n % 3 == 0)
    .mapToObj(list::get)
    .toList();

一个更复杂的解决方案是创建一个自定义收集器,将每个第 n 个项目收集到一个列表中.

A more complicated solution would be to create a custom collector that collects every nth item into a list.

class EveryNth<C> {
    private final int nth;
    private final List<List<C>> lists = new ArrayList<>();
    private int next = 0;

    private EveryNth(int nth) {
        this.nth = nth;
        IntStream.range(0, nth).forEach(i -> lists.add(new ArrayList<>()));
    }

    private void accept(C item) {
        lists.get(next++ % nth).add(item);
    }

    private EveryNth<C> combine(EveryNth<C> other) {
        other.lists.forEach(l -> lists.get(next++ % nth).addAll(l));
        next += other.next;
        return this;
    }

    private List<C> getResult() {
        return lists.get(0);
    }

    public static Collector<Integer, ?, List<Integer>> collector(int nth) {
        return Collector.of(() -> new EveryNth(nth), 
            EveryNth::accept, EveryNth::combine, EveryNth::getResult));
}

这可以如下使用:

Stream.of("Anne", "Bill", "Chris", "Dean", "Eve", "Fred", "George")
    .parallel().collect(EveryNth.collector(3)).toList();

返回结果 [Anne", Dean", George"] 正如您所期望的那样.

Which returns the result ["Anne", "Dean", "George"] as you would expect.

即使使用并行处理,这也是一种非常低效的算法.它将它接受的所有项目分成 n 个列表,然后只返回第一个.不幸的是,它必须在累积过程中保留所有项目,因为直到将它们组合起来,它才知道哪个列表是第 n 个.

This is a very inefficient algorithm even with parallel processing. It splits all items it accepts into n lists and then just returns the first. Unfortunately it has to keep all items through the accumulation process because it's not until they are combined that it knows which list is the nth one.

考虑到收集器解决方案的复杂性和低效率,如果可以的话,我肯定会建议您优先使用上述基于索引的解决方案.如果您不使用支持 get 的集合(例如,您传递的是 Stream 而不是 List),那么您要么需要使用 Collectors.toList 或使用上面的 EveryNth 解决方案收集流.

Given the complexity and inefficiency of the collector solution I would definitely recommend sticking with the indices based solution above in preference to this if you can. If you aren't using a collection that supports get (e.g. you are passed a Stream rather than a List) then you will either need to collect the stream using Collectors.toList or use the EveryNth solution above.

这篇关于从 Java 8 流中取出每个第 n 个元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆