web-dev-qa-db-ja.com

ストリームから連続したペアを収集する

{ 0, 1, 2, 3, 4 }などのストリームを考えると、

どうすればそれを与えられた形式に最もエレガントに変換できますか?

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(もちろん、クラスPairを定義したと仮定します)?

編集:これは厳密にintまたはプリミティブストリームに関するものではありません。答えは、あらゆるタイプのストリームに対して一般的なものでなければなりません。

89

私の 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 質問を参照)。

30
Tagir Valeev

Java 8ストリームライブラリは主に、並列処理のためにストリームを小さなチャンクに分割するためのものであるため、ステートフルパイプラインステージは非常に制限されており、現在のストリーム要素のインデックスを取得したり、隣接するストリーム要素にアクセスしたりすることは、サポートされていません。

もちろん、これらの問題を解決する一般的な方法は、いくつかの制限はありますが、インデックスによってストリームを駆動し、要素を取得できるArrayListなどのランダムアクセスデータ構造で処理される値を持つことに依存します。値がarrayListにある場合、次のようなことを行うことで、要求どおりにペアを生成できます。

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);

もちろん、制限は入力が無限ストリームにならないことです。ただし、このパイプラインは並行して実行できます。

66
Stuart Marks

これはエレガントではなく、ハック的なソリューションですが、無限のストリームで機能します

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)のような、より良い解決策があることを願っています

15
mishadoff

元のスプリッターからすべての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);
14
Tomek Rękawek

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;
}
11
SamTebbs33

これは 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つの要素を持つコレクションを受け入れることができると仮定します。

4でグループ化し、2ずつ増分する場合もサポートされます。

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

Java.util.stream.Stream上のスライディングビューを作成するための同等の静的メソッドもcyclops-streams StreamUtils クラスで提供されます。

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

注:-シングルスレッド操作の場合、ReactiveSeqの方が適切です。 LazyFutureStreamはReactiveSeqを拡張しますが、主に同時/並列使用向けです(先物のストリームです)。

LazyFutureStreamはReactiveSeqを拡張し、ReactiveSeqは素晴らしいjOOλからSeqを拡張します(Java.util.stream.Streamを拡張します)。そのため、Lukasの提示するソリューションはどちらのStreamタイプでも機能します。ウィンドウ/スライディング演算子の主な違いは、明らかな相対的なパワー/複雑さのトレードオフと無限ストリームでの使用に適していることです(スライディングはストリームを消費しませんが、流れるとバッファーになります)。

6
John McClean

プロトンパックライブラリ は、ウィンドウ化された機能を提供します。 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
4
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-タプルを見つける

コメント内の質問は、ペアではなく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λの背後にある会社で働いています

4
Lukas Eder

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();
        });

bufferoperator は、アイテムを放出するObservableを、それらのアイテムのバッファされたコレクションを放出するObservableに変換します。

2
frhack

操作は本質的にステートフルであるため、実際にストリームが解決するものではありません。 javadoc の「ステートレス動作」セクションを参照してください。

最適なアプローチは、ステートフルな動作パラメーターを避けて操作を完全にストリーミングすることです

ここでの解決策の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 + '}'; }
}
1
assylias

あなたの場合、最後に渡されたintを追跡するカスタムIntFunctionを記述し、それを使用して元の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

  }

}
0
jpvee

最後に、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;
}

私が使用するコツは、return right;ステートメントです。

0
Beezer

時系列の時間(x値)の連続的な差を計算するには、streamcollect(...)メソッドを使用します。

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;
    }
}

おそらくあなたのニーズに合わせてこれを変更することができます。

0
Rob Philipp