web-dev-qa-db-ja.com

パーティションa Java 8 Stream

Java 8 Stream?でパーティション分割操作を実装する方法?パーティション分割とは、特定のサイズのサブストリームにストリームを分割することです。どういうわけか、Guava Iterators .partition() メソッド、パーティションはリストではなく遅延評価されたストリームであることが望ましいだけです。

54
Trader001

任意のソースストリームを固定サイズのバッチに分割することはできません。これは、並列処理を台無しにするからです。並列で処理する場合、分割後の最初のサブタスクの要素数がわからない場合があるため、最初のサブタスクが完全に処理されるまで、次のサブタスクのパーティションを作成できません。

ただし、ランダムアクセスListからパーティションのストリームを作成することは可能です。そのような機能は、例えば、私の StreamEx ライブラリで利用可能です:

List<Type> input = Arrays.asList(...);

Stream<List<Type>> stream = StreamEx.ofSubLists(input, partitionSize);

または、ストリームのストリームが本当に必要な場合:

Stream<Stream<Type>> stream = StreamEx.ofSubLists(input, partitionSize).map(List::stream);

サードパーティのライブラリに依存したくない場合は、そのようなofSubListsメソッドを手動で実装できます。

public static <T> Stream<List<T>> ofSubLists(List<T> source, int length) {
    if (length <= 0)
        throw new IllegalArgumentException("length = " + length);
    int size = source.size();
    if (size <= 0)
        return Stream.empty();
    int fullChunks = (size - 1) / length;
    return IntStream.range(0, fullChunks + 1).mapToObj(
        n -> source.subList(n * length, n == fullChunks ? size : (n + 1) * length));
}

この実装は少し長く見えますが、MAX_VALUEに近いリストサイズなどのいくつかのコーナーケースを考慮に入れています。


順序付けされていないストリームの並列フレンドリーなソリューションが必要な場合(したがって、どのストリーム要素が単一のバッチに結合されるかは気にしません)、次のようなコレクターを使用できます(インスピレーションを得るために@sibnickに感謝します):

public static <T, A, R> Collector<T, ?, R> unorderedBatches(int batchSize, 
                   Collector<List<T>, A, R> downstream) {
    class Acc {
        List<T> cur = new ArrayList<>();
        A acc = downstream.supplier().get();
    }
    BiConsumer<Acc, T> accumulator = (acc, t) -> {
        acc.cur.add(t);
        if(acc.cur.size() == batchSize) {
            downstream.accumulator().accept(acc.acc, acc.cur);
            acc.cur = new ArrayList<>();
        }
    };
    return Collector.of(Acc::new, accumulator,
            (acc1, acc2) -> {
                acc1.acc = downstream.combiner().apply(acc1.acc, acc2.acc);
                for(T t : acc2.cur) accumulator.accept(acc1, t);
                return acc1;
            }, acc -> {
                if(!acc.cur.isEmpty())
                    downstream.accumulator().accept(acc.acc, acc.cur);
                return downstream.finisher().apply(acc.acc);
            }, Collector.Characteristics.UNORDERED);
}

使用例:

List<List<Integer>> list = IntStream.range(0,20)
                                    .boxed().parallel()
                                    .collect(unorderedBatches(3, Collectors.toList()));

結果:

[[2, 3, 4], [7, 8, 9], [0, 1, 5], [12, 13, 14], [17, 18, 19], [10, 11, 15], [6, 16]]

このようなコレクターは完全にスレッドセーフであり、シーケンシャルストリームの順序付きバッチを生成します。

すべてのバッチに中間変換を適用する場合は、次のバージョンを使用できます。

public static <T, AA, A, B, R> Collector<T, ?, R> unorderedBatches(int batchSize,
        Collector<T, AA, B> batchCollector,
        Collector<B, A, R> downstream) {
    return unorderedBatches(batchSize, 
            Collectors.mapping(list -> list.stream().collect(batchCollector), downstream));
}

たとえば、この方法では、すべてのバッチの数をその場で合計できます。

List<Integer> list = IntStream.range(0,20)
        .boxed().parallel()
        .collect(unorderedBatches(3, Collectors.summingInt(Integer::intValue), 
            Collectors.toList()));
40
Tagir Valeev

Streamを連続して使用する場合、Streamをパーティション化できます(また、ウィンドウイングなどの関連機能を実行することもできます-これはこの場合に本当に必要なことだと思います)。標準ストリームのパーティショニングをサポートする2つのライブラリは、 cyclops-react (私は著者です)と jOOλ であり、cyclops-reactは(Windowingなどの機能を追加するために)拡張します。

cyclops-streamsには、静的関数のコレクションがあります StreamUtils Java Streams、およびsplitAt、headAndTail、splitBy、パーティショニングのためのパーティションなどの一連の関数を操作するために。

サイズが30のネストされたストリームのストリームにストリームをウィンドウ化するには、ウィンドウメソッドを使用できます。

ストリーミングの用語で言うと、OPsにとって、ストリームを特定のサイズの複数のストリームに分割することは、(パーティション操作ではなく)ウィンドウ操作です。

  Stream<Streamable<Integer>> streamOfStreams = StreamUtils.window(stream,30);

ReactiveSeq と呼ばれるStream拡張クラスがあり、これは jool.Seq を拡張し、ウィンドウ機能を追加します。これにより、コードが少し簡潔になります。

  ReactiveSeq<Integer> seq;
  ReactiveSeq<ListX<Integer>> streamOfLists = seq.grouped(30);

ただし、Tagirが上記で指摘しているように、これは並列ストリームには適していません。マルチスレッド方式で実行するストリームをウィンドウ化またはバッチ化する場合。 cyclops-react のLazyFutureStreamは便利な場合があります(ウィンドウ機能はTo Doリストにありますが、従来のバッチ処理は現在利用可能です)。

この場合、ストリームを実行している複数のスレッドからマルチプロデューサー/シングルコンシューマーのウェイトフリーキューにデータが渡され、そのキューからのシーケンシャルデータは、スレッドに再度配布される前にウィンドウ化されます。

  Stream<List<Data>> batched = new LazyReact().range(0,1000)
                                              .grouped(30)
                                              .map(this::process);
8
John McClean

Jon Skeetが comment で示したように、パーティションを遅延させることはできないようです。非遅延パーティションの場合、すでに次のコードがあります。

public static <T> Stream<Stream<T>> partition(Stream<T> source, int size) {
    final Iterator<T> it = source.iterator();
    final Iterator<Stream<T>> partIt = Iterators.transform(Iterators.partition(it, size), List::stream);
    final Iterable<Stream<T>> iterable = () -> partIt;

    return StreamSupport.stream(iterable.spliterator(), false);
}
7
Trader001

エレガントなソリューションを見つけました:Iterable parts = Iterables.partition(stream::iterator, size)

4
WarGoth

私が見つけたこの問題の最もエレガントで純粋なJava 8ソリューション:

public static <T> List<List<T>> partition(final List<T> list, int batchSize) {
return IntStream.range(0, getNumberOfPartitions(list, batchSize))
                .mapToObj(i -> list.subList(i * batchSize, Math.min((i + 1) * batchSize, list.size())))
                .collect(toList());
}

//https://stackoverflow.com/questions/23246983/get-the-next-higher-integer-value-in-Java
private static <T> int getNumberOfPartitions(List<T> list, int batchSize) {
    return (list.size() + batchSize- 1) / batchSize;
}
2
rloeffel

これは、Listを使用する代わりに遅延評価される純粋なJavaソリューションです。

_public static <T> Stream<List<T>> partition(Stream<T> stream, int batchSize){
    List<List<T>> currentBatch = new ArrayList<List<T>>(); //just to make it mutable 
    currentBatch.add(new ArrayList<T>(batchSize));
    return Stream.concat(stream
      .sequential()                   
      .map(new Function<T, List<T>>(){
          public List<T> apply(T t){
              currentBatch.get(0).add(t);
              return currentBatch.get(0).size() == batchSize ? currentBatch.set(0,new ArrayList<>(batchSize)): null;
            }
      }), Stream.generate(()->currentBatch.get(0).isEmpty()?null:currentBatch.get(0))
                .limit(1)
    ).filter(Objects::nonNull);
}
_

このメソッドは、柔軟性のために_Stream<List<T>>_を返します。 partition(something, 10).map(List::stream)で簡単に_Stream<Stream<T>>_に変換できます。

1
Hei

内部で何らかのハックが可能だと思います。

バッチのユーティリティクラスを作成します。

public static class ConcurrentBatch {
    private AtomicLong id = new AtomicLong();
    private int batchSize;

    public ConcurrentBatch(int batchSize) {
        this.batchSize = batchSize;
    }

    public long next() {
        return (id.getAndIncrement()) / batchSize;
    }

    public int getBatchSize() {
        return batchSize;
    }
}

および方法:

public static <T> void applyConcurrentBatchToStream(Consumer<List<T>> batchFunc, Stream<T> stream, int batchSize){
    ConcurrentBatch batch = new ConcurrentBatch(batchSize);
    //hack Java map: extends and override computeIfAbsent
    Supplier<ConcurrentMap<Long, List<T>>> mapFactory = () -> new ConcurrentHashMap<Long, List<T>>() {
        @Override
        public List<T> computeIfAbsent(Long key, Function<? super Long, ? extends List<T>> mappingFunction) {
            List<T> rs = super.computeIfAbsent(key, mappingFunction);
            //apply batchFunc to old lists, when new batch list is created
            if(rs.isEmpty()){
                for(Entry<Long, List<T>> e : entrySet()) {
                    List<T> batchList = e.getValue();
                    //todo: need to improve
                    synchronized (batchList) {
                        if (batchList.size() == batch.getBatchSize()){
                            batchFunc.accept(batchList);
                            remove(e.getKey());
                            batchList.clear();
                        }
                    }
                }
            }
            return rs;
        }
    };
    stream.map(s -> new AbstractMap.SimpleEntry<>(batch.next(), s))
            .collect(groupingByConcurrent(AbstractMap.SimpleEntry::getKey, mapFactory, mapping(AbstractMap.SimpleEntry::getValue, toList())))
            .entrySet()
            .stream()
            //map contains only unprocessed lists (size<batchSize)
            .forEach(e -> batchFunc.accept(e.getValue()));
}
0
sibnick

AbacusUtil による簡単な解決策を次に示します。

IntStream.range(0, Integer.MAX_VALUE).split(size).forEach(s -> N.println(s.toArray()));

免責事項:私はAbacusUtilの開発者です。

0
user_3380739