给定一个流,例如{ 0, 1, 2, 3, 4 }
,
我如何最优雅地将其转换为给定的形式:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(当然,假设我已经定义了类Pair)?
编辑: 这不仅仅适用于int或原始流。答案应该适用于任何类型的流。
给定一个流,例如{ 0, 1, 2, 3, 4 }
,
我如何最优雅地将其转换为给定的形式:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(当然,假设我已经定义了类Pair)?
编辑: 这不仅仅适用于int或原始流。答案应该适用于任何类型的流。
Java 8的流库主要用于将流分成较小的块以进行并行处理,因此有状态的管道阶段受到了相当大的限制,不能做像获取当前流元素的索引和访问相邻流元素之类的事情。
通常解决这些问题的一种方式(当然有一些限制)是通过索引驱动流,并依赖于某些随机访问数据结构(如ArrayList)来处理正在处理的值。如果值在arrayList
中,则可以通过执行以下操作按请求生成对:
IntStream.range(1, arrayList.size())
.mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
.forEach(System.out::println);
当然,这种方法的限制是输入不能是无限流。但是这个流水线可以并行运行。
我提供的StreamEx库扩展了标准流,为所有流类型提供了pairMap
方法。对于原始流,它不会更改流类型,但可以用于进行一些计算。最常见的用法是计算差异:
int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();
对于对象流,您可以创建任何其他对象类型。我的库不提供任何新的用户可见数据结构,如 Pair
(这是库概念的一部分)。但是,如果您有自己的 Pair
类并希望使用它,可以执行以下操作:
Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);
或者如果您已经有一些Stream
:
Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);
这个功能是使用自定义分割器实现的。它的开销非常低,并且可以很好地并行化。当然,它适用于任何流源,而不仅仅是像许多其他解决方案那样的随机访问列表/数组。在许多测试中,它表现得非常出色。这里有一个JMH基准测试,在这个测试中,我们使用不同的方法查找所有输入值之前的较大值(请参见此问题)。
StreamEx
实现了Iterable
!万岁!) - Aleksandr DubinskyStream
包装成一个 StreamEx
吗? - Aleksandr DubinskypairMap
在顺序流上有序吗?实际上,我想要一个forPairsOrdered()
方法,但是由于没有这样的方法,我能否以某种方式模拟它?是stream.ordered().forPairs()
还是stream().pairMap().forEachOrdered()
? - Askar KalykovpairMap
是具有非干扰无状态映射器函数的中间操作,其排序方式与简单的map
相同。forPairs
按规定是无序的,但对于顺序流而言,无序操作实际上是有序的。如果您将原始问题阐述为单独的stackoverflow问题以提供更多上下文,那将是很好的。 - Tagir Valeevpublic static <T> List<Pair<T, T>> consecutive(List<T> list) {
List<Pair<T, T>> pairs = new LinkedList<>();
list.stream().reduce((a, b) -> {
pairs.add(new Pair<>(a, b));
return b;
});
return pairs;
}
pairs
对象)。因此,如果并发运行,其语义正确性不能得到保证。一个可能的解决方案是使用线程安全的数据结构,比如Vector
。 - Aldan Creoreduce
是一个终端操作。应该可以懒惰地完成这件事。 - Roger Keays这不是优雅的解决方案,而是一个巧妙的应急之策,但适用于无限流。
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
现在您可以将流限制为所需长度
pairStream.limit(1_000_000).forEach(i -> System.out.println(i));
附言:我希望有更好的解决方案,类似于Clojure中的(partition 2 1 stream)
skip
)。@MarioRossi - Aleksandr Dubinsky我实现了一个Spliterator包装器,它从原始Spliterator中获取每个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();
}
}
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);
}
示例用法:
consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
.map(list -> new Pair(list.get(0), list.get(1)))
.forEach(System.out::println);
List<E>
元素的新流。每个列表都包含原始流中的n
个连续元素。自己检查一下 ;) - Tomek RękawekArrayDeque
以提高性能,而不是 LinkedList
。 - Marko Topolnik LazyFutureStream.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
或者
ReactiveSeq.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
.sliding(4,2)
.forEach(System.out::println);
StreamUtils.sliding(Stream.of(1,2,3,4),2)
.map(Pair::new);
Streams.zip(..)
在Guava中可用,供那些依赖它的人使用。
示例:
Streams.zip(list.stream(),
list.stream().skip(1),
(a, b) -> System.out.printf("%s %s\n", a, b));
proton-pack库提供了窗口函数的功能。如果给定一个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(...);
pairs
流包含:(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
st
!这个库能否使用单个流解决问题? - Aleksandr Dubinskywindowed
功能已经添加!请查看编辑。 - Alexis C.如果您愿意使用第三方库并且不需要并行处理,那么jOOλ提供了以下SQL风格的窗口函数:
System.out.println(
Seq.of(0, 1, 2, 3, 4)
.window()
.filter(w -> w.lead().isPresent())
.map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
.toList()
);
产生收益
[(0, 1), (1, 2), (2, 3), (3, 4)]
lead()
函数从窗口中按遍历顺序访问下一个值。
评论中有一个问题询问更一般的解决方案,需要收集n元组(或可能是列表)。因此,这里提供另一种方法:
int n = 3;
System.out.println(
Seq.of(0, 1, 2, 3, 4)
.window(0, n - 1)
.filter(w -> w.count() == n)
.map(w -> w.window().toList())
.toList()
);
[[0, 1, 2], [1, 2, 3], [2, 3, 4]]
如果没有 filter(w -> w.count() == n)
这一步过滤,结果会是:
[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]
w.lead().lead()
吗? - Raul Santelicestuple(w.value(), w.lead(1), w.lead(2))
是一个选项。我已经更新了我的答案,提供了更通用的 length = n
解决方案。 - Lukas Eder.window()
不是一种惰性操作,它会将整个输入流收集到某个中间集合中,然后从中创建一个新的流? - Tagir Valeev这个操作本质上是有状态的,因此并不是流(streams)所要解决的问题 - 请参见 javadoc 中的“无状态行为”部分:
最好完全避免使用带有状态的行为参数来进行流操作。
在这里的一个解决方案是通过外部计数器引入流中的状态,虽然它只能用于顺序流。
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 + '}'; }
}
Stream
!=“lambdas”。 - Aleksandr DubinskyStreamEx
库也是一个不错的发现,本身可能就是一个答案。我对 "streams != lambdas" 的评论是指你所说的 "该操作基本上是有状态的,因此不是 lambda 所要解决的问题。" 我认为你意思想用 "streams" 这个词。 - Aleksandr Dubinsky
list.stream().map(i -> new Pair(i, i+1));
- aepurnietMap.Entry
的任何一种实现作为 Pair 类。(尽管有些人可能认为这是一个 hack,但使用内置类很方便。) - Basil Bourque