从Java 8流中获取每个第n个元素

时间:2015-07-24 04:50:51

标签: java java-8 java-stream

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

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

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

[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]

9 个答案:

答案 0 :(得分:36)

引入Java流的主要动机之一是允许并行操作。这导致要求对诸如mapfilter之类的Java流的操作独立于流中项目的位置或其周围的项目。这具有使得易于分割流以用于并行处理的优点。它的缺点是使某些操作更复杂。

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

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

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

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

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个列表。鉴于其复杂性和低效率,我肯定建议坚持使用上面基于指数的解决方案而不是这个。

答案 1 :(得分:10)

编辑 - 2017年11月28日

正如用户@Emiel在评论中建议的那样,最好的方法是使用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] 通过一系列索引来驱动列表:

Stream.iterate()

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

另一种方法是以下列方式使用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)。

我们必须限制迭代次数,以便我们不会尝试获取边界超出范围的子列表。

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

这也很有效,因为List方法返回原始列表的视图,这意味着它不会为每次迭代创建新的subList()。< / p>

编辑:过了一段时间,我了解到采用@ sprinter的方法之一要好得多,因为StackOverflowError会在原始列表周围创建一个包装器。这意味着流的第二个列表将是第一个列表的包装器,流的第三个列表将是第二个列表的包装器(已经是包装器!),依此类推......

虽然这可能适用于中小型列表,但应注意,对于非常大的源列表,将创建许多包装器。这可能最终会变得昂贵,甚至产生<html> <head> <META http-equiv="Content-Type" content="text/html; charset=utf-8"> <style type="text/css"> table { table-layout:auto; border-collapse:collapse; border:thin solid black; font-family:Arial; } th { } .Headline { padding:0pt; font-size:9pt; background-color:#FFFF00; color:#FF0000; border:thin solid black; vertical-align:top; } tbody { border:1px solid Black; } tbody:nth-child(odd) { background: RGB(255, 255, 220); } tbody:nth-child(even) { background: RGB(238, 255, 255); } td { vertical-align:top; border:thin dotted Silver; padding:0pt; font-size:8pt; padding-right:3px; padding-left:3px; mso-number-format:"\@"; } pre { margin:0; padding:0; white-space:pre-wrap; font-size:8pt; font-family:Arial; } </style> </head> <body> <table> <th class="Headline">TC #</th> <th class="Headline">Steps</th> <th class="Headline">Details</th> <th class="Headline">Timestamp</th> <tbody> <tr> <td class="UCNumber">1</td> <td class="Level1">▬▬ UC_010 ▬▬</td> <td><pre>Product Multiple; Event = NEW</pre></td> <td>01/04/2015 - 10:43:54</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_SETUP</td> <td><pre>Executing script steps of TD_SETUP</pre></td> <td>01/04/2015 - 10:43:54</td> </tr> <tr> <td></td> <td class="Level1">Statement Skipped: #NEWAMEND</td> <td><pre>0</pre> </td> <td>01/04/2015 - 10:43:55</td> </tr> <tr> <td></td> <td class="Level1">Statement Skipped: #OUTBOUND</td> <td><pre>2</pre> </td> <td>01/04/2015 - 10:43:55</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_VERIFYNEWAMEND</td> <td><pre>Executing script steps of TD_VERIFYNEWAMEND</pre></td> <td>01/04/2015 - 10:44:01</td> </tr> </tbody> <tbody> <tr> <td class="UCNumber">2</td> <td class="Level1">▬▬ UC_011 ▬▬</td> <td><pre>Product Multiple; Event = AMEND</pre></td> <td>01/04/2015 - 10:45:14</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_SETUP</td> <td><pre>Executing script steps of TD_SETUP</pre></td> <td>01/04/2015 - 10:45:14</td> </tr> <tr> <td></td> <td class="Level1">Statement Skipped: #NEWAMEND</td> <td><pre>0</pre></td> <td>01/04/2015 - 10:45:14</td> </tr> <tr> <td></td> <td class="Level1">Statement Skipped: #OUTBOUND</td> <td><pre>2</pre></td> <td>01/04/2015 - 10:45:14</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_VERIFYNEWAMEND</td> <td><pre>Executing script steps of TD_VERIFYNEWAMEND</pre></td> <td>01/04/2015 - 10:45:20</td> </tr> </tbody> <tbody> <tr> <td class="UCNumber">3</td> <td class="Level1">▬▬ UC012 ▬▬</td> <td><pre>Product Multiple; Event = CANCEL</pre></td> <td>01/04/2015 - 10:46:34</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_SETUP</td> <td><pre>Executing script steps of TD_SETUP</pre></td> <td>01/04/2015 - 10:46:34</td> </tr> <tr> <td></td> <td class="Level1">Statement Skipped: #CANCEL</td> <td><pre>3</pre></td> <td>01/04/2015 - 10:46:34</td> </tr> <tr> <td></td> <td class="Level1">●●● TD_VERIFYCANCEL</td> <td><pre>Executing script steps of TD_VERIFYCANCEL</pre></td> <td>01/04/2015 - 10:46:40</td> </tr> </tbody> </table> </body> </html>

答案 2 :(得分:7)

如果您愿意使用第三方库,则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λ背后的公司工作

答案 3 :(得分:2)

您还可以将flatMap与跳过项目的自定义函数一起使用:

private <T> Function<T, Stream<T>> everyNth(int n) {
  return new Function<T, Stream<T>>() {
    int i = 0;

    @Override
    public Stream<T> apply(T t) {
      if (i++ % n == 0) {
        return Stream.of(t);
      }
      return Stream.empty();
    }
  };
}

@Test
public void everyNth() {
  assertEquals(
    Arrays.asList(1, 4, 7, 10),
    IntStream.rangeClosed(1, 10).boxed()
      .flatMap(everyNth(3))
      .collect(Collectors.toList())
  );
}

它具有使用非索引流的优点。但是将它与并行流一起使用并不是一个好主意(可能会切换到i的原子整数)。

答案 4 :(得分:1)

试试这个。

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    int[] n = {0};
    List<Integer> result = list.stream()
        .filter(x -> n[0]++ % 3 == 0)
        .collect(Collectors.toList());
    System.out.println(result);
    // -> [1, 4, 7, 10]

答案 5 :(得分:1)

以下是AbacusUtil

的代码
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .filter(MutableInt.of(0), (e, idx) -> idx.getAndDecrement() % 2 == 0)
        .println();
// output: 1, 3, 5, 7, 9

或者如果需要索引:

Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      .indexed().filter(i -> i.index() % 2 == 0).println();
// output: [0]=1, [2]=3, [4]=5, [6]=7, [8]=9

声明:我是AbacusUtil的开发者。

答案 6 :(得分:1)

使用番石榴:

Streams
    .mapWithIndex(stream, SimpleImmutableEntry::new)
    .filter(entry -> entry.getValue() % 3 == 0)
    .map(Entry::getKey)
    .collect(Collectors.toList());

答案 7 :(得分:0)

你能试试吗

employees.stream()
.filter(e -> e.getName().charAt(0) == 's')
.skip(n-1)
.findFirst()

答案 8 :(得分:0)

我来自How to avoid memory overflow using high throughput JAVA I/O Stream from JDBC connectors?,这表明您担心脚印。

因此,我建议采用以下解决方案,该方案的垃圾回收率应该较低

int[] counter = new int[]{0};

list.stream()
.filter(l -> counter[0]++ % n == 0)

当然,您需要确保流不是并行的。