假设我有一个这样的列表:
[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]
答案 0 :(得分:36)
引入Java流的主要动机之一是允许并行操作。这导致要求对诸如map
和filter
之类的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)
当然,您需要确保流不是并行的。