当前位置: 首页 > 知识库问答 >
问题:

从Java8流中获取每n个元素

包子航
2023-03-14

假设我有这样一个列表:

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

是否可以使用Java8流从该列表中每隔一秒获取一个元素以获得以下内容?

[1, 3, 5, 7, 9]

或者甚至每三个元素?

[1, 4, 7, 10]

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

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]

共有3个答案

滕胜涝
2023-03-14

如果您愿意使用第三方库,那么jOOλ提供了一些有用的功能,如zipWithIndex()

System.out.println(
Seq.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
   .zipWithIndex()             // This produces a Tuple2(yourvalue, index)
   .filter(t -> t.v2 % 2 == 0) // Filter by the index
   .map(t -> t.v1)             // Remove the index again
   .toList()
);
[1, 3, 5, 7, 9]
System.out.println(
Seq.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
   .zipWithIndex()
   .filter(t -> t.v2 % 3 == 0)
   .map(t -> t.v1)
   .toList()
);
[1, 4, 7, 10]

免责声明:我为jOOλ背后的公司工作

郎子平
2023-03-14

编辑-2017年11月28日

正如user@Emiel在评论中建议的那样,最好的方法是使用Stream.itearate通过一系列索引驱动列表:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int skip = 3;
int size = list.size();
// Limit to carefully avoid IndexOutOfBoundsException
int limit = size / skip + Math.min(size % skip, 1);

List<Integer> result = Stream.iterate(0, i -> i + skip)
    .limit(limit)
    .map(list::get)
    .collect(Collectors.toList());

System.out.println(result); // [1, 4, 7, 10]

这种方法没有我前面的答案的缺点,下面是我的答案(出于历史原因,我决定保留它)。

另一种方法是使用流。迭代()方法如下:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int skip = 3;
int size = list.size();
// Limit to carefully avoid IndexOutOfBoundsException
int limit = size / skip + Math.min(size % skip, 1);

List<Integer> result = Stream.iterate(list, l -> l.subList(skip, l.size()))
    .limit(limit)
    .map(l -> l.get(0))
    .collect(Collectors.toList());

System.out.println(result); // [1, 4, 7, 10]

其思想是创建一个子列表流,每个子列表跳过前一个子列表的第一个N元素(N=3)。

我们必须限制迭代次数,这样我们就不会试图得到边界超出范围的子列表。

然后,我们将我们的子列表映射到它们的第一个元素并收集我们的结果。根据源列表,保持每个子列表的第一个元素按预期工作,因为每个子列表的开始索引都向右移动了N元素。

这也是有效的,因为列表。sublist()方法返回原始列表的视图,这意味着它不会为每个迭代创建新的列表。

编辑:过了一段时间,我了解到最好采用@sprinter的任何一种方法,因为subList()会围绕原始列表创建一个包装器。这意味着流的第二个列表将是第一个列表的包装器,流的第三个列表将是第二个列表的包装器(已经是包装器!),等等

虽然这可能适用于中小型列表,但应该注意的是,对于非常大的源列表,将创建许多包装器。这最终可能是昂贵的,甚至产生一个StackOverflow Error

酆晔
2023-03-14

引入Java流的主要动机之一是允许并行操作。这就要求Java流上的操作,如mapfilter与流中的项目或其周围的项目的位置无关。这样做的优点是可以很容易地分割流进行并行处理。它的缺点是使某些操作更加复杂。

因此,简单的答案是,没有简单的方法来做一些事情,比如每N个项目或将每个项目映射到所有之前项目的总和。

List<String> list = ...;
return IntStream.range(0, list.size())
    .filter(n -> n % 3 == 0)
    .mapToObj(list::get)
    .toList();
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”]

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

考虑到收集器解决方案的复杂性和低效性,如果可以的话,我绝对建议坚持使用上面基于索引的解决方案。如果您没有使用支持get集合(例如,您收到的是而不是列表),则您需要使用收集器收集流。t列出或使用上面的EveryNth解决方案。

 类似资料:
  • 问题内容: 我想知道是否有替代 使用 流 ? 问题答案: 定制收集器可以这样写: 并像这样使用它:

  • 本文向大家介绍Elm从列表中获取第n个元素,包括了Elm从列表中获取第n个元素的使用技巧和注意事项,需要的朋友参考一下 示例 List不支持“随机访问”,这意味着要从列表中获取第五个元素要比第一个元素花费更多的工作,因此没有任何List.get nth list功能。必须从头开始(1 -> 2 -> 3 -> 4 -> 5)。 如果您需要随机访问,则使用随机访问数据结构(例如)可能会获得更好的结果

  • 问题内容: 我正在使用Javascript(ES6)/ FaceBook进行反应,并尝试获取大小变化的数组的前3个元素。我想做相当于Linq take(n)。 在我的Jsx文件中,我有以下内容: 然后得到我尝试的前三个项目 这不起作用,因为地图没有设置功能。 你能帮忙吗? 问题答案: 我相信您正在寻找的是:

  • 从 array 中获取 n 个唯一键随机元素。 使用Fisher-Yates算法 对数组进行打乱。 使用 Array.slice() 获取第一个 n 元素。 省略第二个参数,n 从数组中随机取得 1 个元素。 const sampleSize = ([...arr], n = 1) => { let m = arr.length; while (m) { const i = Mat

  • 问题内容: 我正在研究“如何从javascript中的数组随机访问元素”。我发现了许多与此有关的链接。 问题: 但是在这种情况下,我们只能从数组中选择一项,如果我们想要多个元素,那么我们将如何实现这一点,所以请仅从该语句中获取一个数组中的多个元素。 问题答案: 尝试以下无损快速功能:

  • 返回数组中的每个第 n 个元素。 使用 Array.filter() 创建一个包含给定数组的每个第 n 个元素的新数组。 const everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1); everyNth([1, 2, 3, 4, 5, 6], 2); // [ 2, 4, 6 ]