のようなストリームが与えられると{ 0, 1, 2, 3, 4 }
、
それを特定の形式に最もエレガントに変換するにはどうすればよいですか:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(もちろん、クラスペアを定義したと仮定します)?
編集:これは厳密には int やプリミティブ ストリームに関するものではありません。答えは、あらゆるタイプのストリームに対して一般的なものでなければなりません。
のようなストリームが与えられると{ 0, 1, 2, 3, 4 }
、
それを特定の形式に最もエレガントに変換するにはどうすればよいですか:
{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }
(もちろん、クラスペアを定義したと仮定します)?
編集:これは厳密には 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 ベンチマークです (この質問を参照)。
これはエレガントではなく、ハックなソリューションですが、無限のストリームで機能します
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));
PSクロージュアのようなより良い解決策があることを願っています(partition 2 1 stream)
元のスプリッテレータからすべて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);
RxJava (非常に強力なリアクティブ拡張ライブラリ)を使用できます
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();
});
バッファ オペレータは、アイテムを発行する Observable を、それらのアイテムのバッファリングされたコレクションを発行する Observable に変換します。
操作は本質的にステートフルであるため、実際にはどのストリームが解決されるわけではありません - javadocの「Stateless Behaviors」セクションを参照してください。
最善のアプローチは、操作を完全にストリーミングするためにステートフルな動作パラメータを避けることです
ここでの 1 つの解決策は、シーケンシャル ストリームでのみ機能しますが、外部カウンターを介してストリームに状態を導入することです。
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 + '}'; }
}
洗練された解決策は、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)
);
これは非常に簡潔で洗練されていますが、リストを入力として使用しています。無限ストリーム ソースは、この方法では処理できません。
もう 1 つの (もっと厄介な) 問題は、最近 API から削除された Streams クラス全体と一緒に圧縮されていることです。上記のコードは、b95以前のリリースでのみ機能します。したがって、最新の JDK では、洗練された FP スタイルのソリューションは存在しないと言えます。現時点では、何らかの方法で zip が API に再導入されることを期待できます。
これは興味深い問題です。私のハイブリッドの試みは何か良いですか?
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);
}
並列処理には向かないので、失格になる可能性があると思います。
制限付きキューを使用して、ストリームを流れる要素を格納することで、これを実現できます (これは、ここで詳しく説明したアイデアに基づいています: Is it possible to get next element in the Stream? )
以下の例では、最初に、ストリームを通過する要素を格納する BoundedQueue クラスのインスタンスを定義します (LinkedList を拡張するという考えが気に入らない場合は、代替のより一般的なアプローチについて上記のリンクを参照してください)。後で、2 つの後続の要素をペアのインスタンスに結合するだけです。
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);
}
}
他の人が観察したように、問題の性質上、ある程度のステートフルネスが必要です。
私は同様の問題に直面しました。そこでは、本質的に 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();
}
ストリームの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
}