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

查看:268
本文介绍了从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]

或者甚至每隔三个元素?

Or maybe even every third element?

[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流(例如 map filter )的操作独立于项目的位置流或周围的项目。这具有使分割流易于并行处理的优点。它的缺点是使某些操作更复杂。

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.

所以简单的答案就是没有简单的方法来做一些事情,比如每个项目或每个项目的映射以前所有项目的总和。

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)
    .collect(Collectors.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));
}

可以按如下方式使用:

List<String> list = Arrays.asList("Anne", "Bill", "Chris", "Dean", "Eve", "Fred", "George");
list.stream().parallel().collect(EveryNth.collector(3)).forEach(System.out::println);

返回您期望的结果。

即使使用并行处理,这也是一种非常低效的算法。它将它接受的所有项目拆分为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. Given its complexity and inefficiency I would definitely recommending sticking with the indices based solution above in preference to this.

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

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