Java 从流中收集连续的对
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20470010/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Collect successive pairs from a stream
提问by Aleksandr Dubinsky
Given a stream such as { 0, 1, 2, 3, 4 }
,
给定一个流,例如{ 0, 1, 2, 3, 4 }
,
how can I most elegantly transform it into given form:
我怎样才能最优雅地将它转换成给定的形式:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(assuming, of course, I've defined class Pair)?
(当然,假设我已经定义了类 Pair)?
Edit:This isn't strictly about ints or primitive streams. The answer should be general for a stream of any type.
编辑:这不是严格意义上的整数或原始流。对于任何类型的流,答案应该是通用的。
采纳答案by Tagir Valeev
My StreamExlibrary which extends standard streams provides a pairMap
method for all stream types. For primitive streams it does not change the stream type, but can be used to make some calculations. Most common usage is to calculate differences:
我的StreamEx库扩展了标准流,pairMap
为所有流类型提供了一种方法。对于原始流,它不会改变流类型,但可用于进行一些计算。最常见的用法是计算差异:
int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();
For object stream you can create any other object type. My library does not provide any new user-visible data structures like Pair
(that's the part of library concept). However if you have your own Pair
class and want to use it, you can do the following:
对于对象流,您可以创建任何其他对象类型。我的图书馆不提供任何新的用户可见的数据结构Pair
(这是图书馆概念的一部分)。但是,如果您有自己的Pair
类并想使用它,则可以执行以下操作:
Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);
Or if you already have some Stream
:
或者,如果您已经有一些Stream
:
Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);
This functionality is implemented using custom spliterator. It has quite low overhead and can parallelize nicely. Of course it works with any stream source, not just random access list/array like many other solutions. In many tests it performs really well. Here'sa JMH benchmark where we find all input values preceding a larger value using different approaches (see thisquestion).
此功能是使用自定义 spliterator实现的。它的开销非常低,并且可以很好地并行化。当然,它适用于任何流源,而不仅仅是像许多其他解决方案那样的随机访问列表/数组。在许多测试中它表现得非常好。这是一个 JMH 基准测试,我们使用不同的方法找到较大值之前的所有输入值(请参阅此问题)。
回答by Savv
Run a for
loop that runs from 0 to length-1
of your stream
运行for
从 0 到length-1
流的循环
for(int i = 0 ; i < stream.length-1 ; i++)
{
Pair pair = new Pair(stream[i], stream[i+1]);
// then add your pair to an array
}
回答by mishadoff
This is not elegant, it's a hackish solution, but works for infinite streams
这并不优雅,它是一种骇人听闻的解决方案,但适用于无限流
Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
new Function<Integer, Pair>() {
Integer previous;
@Override
public Pair apply(Integer integer) {
Pair pair = null;
if (previous != null) pair = new Pair(previous, integer);
previous = integer;
return pair;
}
}).skip(1); // drop first null
Now you can limit your stream to the length you want
现在您可以将流限制为您想要的长度
pairStream.limit(1_000_000).forEach(i -> System.out.println(i));
P.S.I hope there is better solution, something like clojure (partition 2 1 stream)
PS我希望有更好的解决方案,比如clojure(partition 2 1 stream)
回答by jpvee
In your case, I would write my custom IntFunction which keeps track of the last int passed and use that to map the original IntStream.
在你的情况下,我会编写我的自定义 IntFunction 来跟踪最后传递的 int 并使用它来映射原始 IntStream。
import java.util.function.IntFunction;
import java.util.stream.IntStream;
public class PairFunction implements IntFunction<PairFunction.Pair> {
public static class Pair {
private final int first;
private final int second;
public Pair(int first, int second) {
this.first = first;
this.second = second;
}
@Override
public String toString() {
return "[" + first + "|" + second + "]";
}
}
private int last;
private boolean first = true;
@Override
public Pair apply(int value) {
Pair pair = !first ? new Pair(last, value) : null;
last = value;
first = false;
return pair;
}
public static void main(String[] args) {
IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
final PairFunction pairFunction = new PairFunction();
intStream.mapToObj(pairFunction)
.filter(p -> p != null) // filter out the null
.forEach(System.out::println); // display each Pair
}
}
回答by Tomek R?kawek
I've implemented a spliterator wrapper which takes every n
elements T
from the original spliterator and produces List<T>
:
我已经实现了一个拆分器包装器,它从原始拆分器中获取每个n
元素T
并生成List<T>
:
public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {
private final Spliterator<T> wrappedSpliterator;
private final int n;
private final Deque<T> deque;
private final Consumer<T> dequeConsumer;
public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
this.wrappedSpliterator = wrappedSpliterator;
this.n = n;
this.deque = new ArrayDeque<>();
this.dequeConsumer = deque::addLast;
}
@Override
public boolean tryAdvance(Consumer<? super List<T>> action) {
deque.pollFirst();
fillDeque();
if (deque.size() == n) {
List<T> list = new ArrayList<>(deque);
action.accept(list);
return true;
} else {
return false;
}
}
private void fillDeque() {
while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
;
}
@Override
public Spliterator<List<T>> trySplit() {
return null;
}
@Override
public long estimateSize() {
return wrappedSpliterator.estimateSize();
}
@Override
public int characteristics() {
return wrappedSpliterator.characteristics();
}
}
Following method may be used to create a consecutive stream:
以下方法可用于创建连续流:
public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
Spliterator<E> spliterator = stream.spliterator();
Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
return StreamSupport.stream(wrapper, false);
}
Sample usage:
示例用法:
consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
.map(list -> new Pair(list.get(0), list.get(1)))
.forEach(System.out::println);
回答by gadget
An elegant solution would be to use zip. Something like:
一个优雅的解决方案是使用zip。就像是:
List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
input.stream().substream(1),
(a, b) -> new Pair(a, b)
);
This is pretty concise and elegant, however it uses a list as an input. An infinite stream source cannot be processed this way.
这是非常简洁和优雅的,但是它使用列表作为输入。无法以这种方式处理无限流源。
Another (lot more troublesome) issue is that zip together with the entire Streams class has been lately removed from the API. The above code only works with b95or older releases. So with the latest JDK I would say there is no elegant FP style solution and right now we can just hope that in some way zip will be reintroduced to the API.
另一个(更麻烦的)问题是 zip 与整个 Streams 类最近已从 API 中删除。上面的代码只适用于b95或更早的版本。因此,对于最新的 JDK,我想说没有优雅的 FP 风格解决方案,现在我们只能希望以某种方式将 zip 重新引入 API。
回答by Stuart Marks
The Java 8 streams library is primarily geared toward splitting streams into smaller chunks for parallel processing, so stateful pipeline stages are quite limited, and doing things like getting the index of the current stream element and accessing adjacent stream elements are not supported.
Java 8 流库主要面向将流拆分为更小的块以进行并行处理,因此有状态的流水线阶段非常有限,并且不支持获取当前流元素的索引和访问相邻流元素等操作。
A typical way to solve these problems, with some limitations, of course, is to drive the stream by indexes and rely on having the values being processed in some random-access data structure like an ArrayList from which the elements can be retrieved. If the values were in arrayList
, one could generate the pairs as requested by doing something like this:
解决这些问题的典型方法当然有一些限制,但有一些限制,是通过索引驱动流,并依赖于在一些随机访问数据结构(如可以从中检索元素的 ArrayList)中处理值。如果值在 中arrayList
,则可以通过执行以下操作来根据请求生成对:
IntStream.range(1, arrayList.size())
.mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
.forEach(System.out::println);
Of course the limitation is that the input cannot be an infinite stream. This pipeline can be run in parallel, though.
当然,限制是输入不能是无限流。不过,这个管道可以并行运行。
回答by assylias
The operation is essentially stateful so not really what streams are meant to solve - see the "Stateless Behaviors" section in the javadoc:
该操作本质上是有状态的,因此并不是真正要解决的流 - 请参阅javadoc 中的“无状态行为”部分:
The best approach is to avoid stateful behavioral parameters to stream operations entirely
最好的方法是避免有状态的行为参数完全流式传输操作
One solution here is to introduce state in your stream through an external counter, although it will only work with a sequential stream.
这里的一种解决方案是通过外部计数器在您的流中引入状态,尽管它仅适用于顺序流。
public static void main(String[] args) {
Stream<String> strings = Stream.of("a", "b", "c", "c");
AtomicReference<String> previous = new AtomicReference<>();
List<Pair> collect = strings.map(n -> {
String p = previous.getAndSet(n);
return p == null ? null : new Pair(p, n);
})
.filter(p -> p != null)
.collect(toList());
System.out.println(collect);
}
static class Pair<T> {
private T left, right;
Pair(T left, T right) { this.left = left; this.right = right; }
@Override public String toString() { return "{" + left + "," + right + '}'; }
}
回答by Alexis C.
The proton-pack libraryprovides the windowed functionnality. Given a Pair class and a Stream, you can do it like this:
该质子包库提供了窗口functionnality。给定一个 Pair 类和一个 Stream,你可以这样做:
Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
.map(l -> new Pair<>(l.get(0), l.get(1)))
.moreStreamOps(...);
Now the pairs
stream contains:
现在pairs
流包含:
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
回答by John McClean
You can do this in cyclops-react(I contribute to this library), using the sliding operator.
您可以在cyclops-react(我为该库做出贡献)中使用滑动运算符执行此操作。
LazyFutureStream.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
Or
或者
ReactiveSeq.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
Assuming the Pair constructor can accept a Collection with 2 elements.
假设 Pair 构造函数可以接受具有 2 个元素的 Collection。
If you wanted to group by 4, and increment by 2 that is also supported.
如果您想按 4 分组,并按 2 递增,这也受支持。
ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
.sliding(4,2)
.forEach(System.out::println);
Equivalant static methods for creating a sliding view over java.util.stream.Stream are also provided in cyclops-streams StreamUtilsclass.
Cyclops-streams StreamUtils类中还提供了用于在 java.util.stream.Stream 上创建滑动视图的等效静态方法。
StreamUtils.sliding(Stream.of(1,2,3,4),2)
.map(Pair::new);
Note :- for single-threaded operation ReactiveSeq would be more appropriate. LazyFutureStream extends ReactiveSeq but is primarily geared for concurrent / parallel use (it is a Stream of Futures).
注意:- 对于单线程操作 ReactiveSeq 会更合适。LazyFutureStream 扩展了 ReactiveSeq,但主要面向并发/并行使用(它是一个 Futures 流)。
LazyFutureStream extends ReactiveSeq which extends Seq from the awesome jOOλ (which extends java.util.stream.Stream), so the solutions Lukas' presents would also work with either Stream type. For anyone interested the primary differences between the window / sliding operators are the obvious relative power / complexity trade off and suitability for use with infinite streams (sliding doesn't consume the stream, but buffers as it flows).
LazyFutureStream 扩展了 ReactiveSeq,后者从令人敬畏的 jOOλ(扩展了 java.util.stream.Stream)扩展了 Seq,因此 Lukas 提出的解决方案也适用于任一 Stream 类型。对于任何感兴趣的人来说,窗口/滑动运算符之间的主要区别是明显的相对功率/复杂性权衡以及适用于无限流的适用性(滑动不消耗流,而是在流时缓冲)。
回答by user190364
I agree with @aepurniet but instead map you have to use mapToObj
我同意@aepurniet 但你必须使用 mapToObj 来映射
range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);