给定{ 0, 1, 2, 3, 4 }
,
我怎样才能最优雅地将其转换为给定的形式:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(当然,假设我已经定义了类对)?
编辑:这不是严格意义上的整数或原始流。对于任何类型的流,答案应该是通用的。
答案 0 :(得分:64)
Java 8流库主要用于将流拆分为较小的块以进行并行处理,因此有状态的流水线阶段非常有限,并且不支持获取当前流元素的索引和访问相邻流元素等操作。
解决这些问题的典型方法当然有一些限制,就是通过索引来驱动流,并依赖于在某些随机访问数据结构中处理值,如ArrayList,可以从中检索元素。如果值在arrayList
中,则可以通过执行以下操作生成所需的对:
IntStream.range(1, arrayList.size())
.mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
.forEach(System.out::println);
当然,限制是输入不能是无限流。不过,这个管道可以并行运行。
答案 1 :(得分:30)
扩展标准流的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);
此功能使用custom spliterator实现。它的开销很低,可以很好地并行化。当然它适用于任何流源,而不仅仅是随机访问列表/数组,就像许多其他解决方案一样。在许多测试中,它表现得非常好。 Here's一个JMH基准,我们使用不同的方法找到更大值之前的所有输入值(参见this问题)。
答案 2 :(得分:15)
这不是优雅的,它是一种黑客的解决方案,但适用于无限流
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));
P.S。我希望有更好的解决方案,比如clojure (partition 2 1 stream)
答案 3 :(得分:14)
我已经实现了一个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);
答案 4 :(得分:8)
您可以使用Stream.reduce()方法执行此操作(我还没有看到使用此技术的任何其他答案)。
public 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;
}
答案 5 :(得分:6)
您可以使用滑动操作符在cyclops-react(我为此库做出贡献)中执行此操作。
LazyFutureStream.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
或
ReactiveSeq.of( 0, 1, 2, 3, 4 )
.sliding(2)
.map(Pair::new);
假设Pair构造函数可以接受包含2个元素的Collection。
如果你想按4分组,又增加2,也支持。
ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
.sliding(4,2)
.forEach(System.out::println);
在cyclops-streams StreamUtils类中还提供了用于在java.util.stream.Stream上创建滑动视图的Equivalant静态方法。
StreamUtils.sliding(Stream.of(1,2,3,4),2)
.map(Pair::new);
注意: - 对于单线程操作,ReactiveSeq会更合适。 LazyFutureStream扩展了ReactiveSeq,但主要用于并发/并行使用(它是一个期货流)。
LazyFutureStream扩展了ReactiveSeq,它从令人敬畏的jOOλ(扩展了java.util.stream.Stream)扩展了Seq,因此Lukas提供的解决方案也适用于Stream类型。对于任何感兴趣的人来说,窗口/滑动运算符之间的主要区别在于明显的相对功率/复杂度权衡以及与无限流一起使用的适合性(滑动不消耗流,而是流动时的缓冲区)。
答案 6 :(得分:4)
proton-pack library提供了窗口功能。给定一个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
答案 7 :(得分:4)
如果您愿意使用第三方库而不需要并行性,那么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]]
免责声明:我为jOOλ背后的公司工作
答案 8 :(得分:2)
我们可以使用RxJava(非常强大的reactive extension库)
IntStream intStream = IntStream.iterate(1, n -> n + 1);
Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);
pairObservable.take(10).forEach(b -> {
b.forEach(n -> System.out.println(n));
System.out.println();
});
缓冲区 operator转换一个Observable 将项目发送到Observable中,该Observable发出缓冲的集合 那些物品..
答案 9 :(得分:1)
Streams.zip(..)
is available in Guava,供那些依赖它的人使用。
示例:
Streams.zip(list.stream(),
list.stream().skip(1),
(a, b) -> System.out.printf("%s %s\n", a, b));
答案 10 :(得分:0)
操作本质上是有状态的,所以不是什么流意味着解决 - 请参阅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 + '}'; }
}
答案 11 :(得分:0)
在你的情况下,我会编写我的自定义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
}
}
答案 12 :(得分:0)
为了计算时间序列的时间(x值)的连续差异,我使用stream
&#39; collect(...)
方法:
final List< Long > intervals = timeSeries.data().stream()
.map( TimeSeries.Datum::x )
.collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
.intervals();
DifferenceCollector的位置如下:
public class DifferenceCollector implements LongConsumer
{
private final List< Long > intervals = new ArrayList<>();
private Long lastTime;
@Override
public void accept( final long time )
{
if( Objects.isNull( lastTime ) )
{
lastTime = time;
}
else
{
intervals.add( time - lastTime );
lastTime = time;
}
}
public void combine( final DifferenceCollector other )
{
intervals.addAll( other.intervals );
lastTime = other.lastTime;
}
public List< Long > intervals()
{
return intervals;
}
}
您可以修改它以满足您的需求。
答案 13 :(得分:0)
我终于找到了一种欺骗Stream.reduce的方法,使其能够整齐地处理成对的值;有许多用例需要这种功能,而这些用例在JDK 8中自然不会出现:
public static int ArithGeo(int[] arr) {
//Geometric
List<Integer> diffList = new ArrayList<>();
List<Integer> divList = new ArrayList<>();
Arrays.stream(arr).reduce((left, right) -> {
diffList.add(right-left);
divList.add(right/left);
return right;
});
//Arithmetic
if(diffList.stream().distinct().count() == 1) {
return 1;
}
//Geometric
if(divList.stream().distinct().count() == 1) {
return 2;
}
return -1;
}
我使用的诀窍是返回权利; 声明。
答案 14 :(得分:-1)
这是一个有趣的问题。我的混合尝试是否低于任何好处?
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
Iterator<Integer> first = list.iterator();
first.next();
if (first.hasNext())
list.stream()
.skip(1)
.map(v -> new Pair(first.next(), v))
.forEach(System.out::println);
}
我认为它不适合并行处理,因此可能被取消资格。
答案 15 :(得分:-1)
正如其他人所观察到的那样,由于问题的性质,需要一些有状态。
我遇到了类似的问题,其中我想要的本质上是Oracle SQL函数LEAD。我在下面尝试实现它。
/**
* Stream that pairs each element in the stream with the next subsequent element.
* The final pair will have only the first item, the second will be null.
*/
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
final Iterator<T> input = stream.sequential().iterator();
final Iterable<Pair<T>> iterable = () ->
{
return new Iterator<Pair<T>>()
{
Optional<T> current = getOptionalNext(input);
@Override
public boolean hasNext()
{
return current.isPresent();
}
@Override
public Pair<T> next()
{
Optional<T> next = getOptionalNext(input);
final Pair<T> pair = next.isPresent()
? new Pair(current.get(), next.get())
: new Pair(current.get(), null);
current = next;
return pair;
}
};
};
return iterable.spliterator();
}
private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
return iterator.hasNext()
? Optional.of(iterator.next())
: Optional.empty();
}
答案 16 :(得分:-1)
你可以通过使用一个有界队列来存储流经流的元素(这是基于我在这里详细描述的想法:Is it possible to get next element in the Stream?)
Belows示例首先定义BoundedQueue类的实例,它将存储通过流的元素(如果您不想扩展LinkedList,请参阅上面提到的链接以获得替代和更通用的方法)。稍后您只需将两个后续元素组合到Pair的实例中:
public class TwoSubsequentElems {
public static void main(String[] args) {
List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));
class BoundedQueue<T> extends LinkedList<T> {
public BoundedQueue<T> save(T curElem) {
if (size() == 2) { // we need to know only two subsequent elements
pollLast(); // remove last to keep only requested number of elements
}
offerFirst(curElem);
return this;
}
public T getPrevious() {
return (size() < 2) ? null : getLast();
}
public T getCurrent() {
return (size() == 0) ? null : getFirst();
}
}
BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();
final List<Pair<Integer>> answer = input.stream()
.map(i -> streamHistory.save(i))
.filter(e -> e.getPrevious() != null)
.map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
.collect(Collectors.toList());
answer.forEach(System.out::println);
}
}
答案 17 :(得分:-1)
优雅的解决方案是使用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)
);
这是非常简洁和优雅,但它使用列表作为输入。无限流源无法以这种方式处理。
另一个(更麻烦的)问题是最近从API中删除了整个Streams类的zip。以上代码仅适用于b95或更早版本。因此,对于最新的JDK,我会说没有优雅的FP风格解决方案,现在我们可以希望以某种方式将zip重新引入API。
答案 18 :(得分:-3)
我同意@aepurniet 但相反,你必须使用mapToObj
range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);
答案 19 :(得分:-5)
运行从您的信息流的0到for
的{{1}}循环
length-1