Gibt es ein allgemeines Java-Dienstprogramm, um eine Liste in Stapel aufzuteilen?

140

Ich habe mir ein Dienstprogramm geschrieben, um eine Liste in Stapel mit einer bestimmten Größe aufzuteilen. Ich wollte nur wissen, ob es dafür bereits Apache Commons gibt.

public static <T> List<List<T>> getBatches(List<T> collection,int batchSize){
    int i = 0;
    List<List<T>> batches = new ArrayList<List<T>>();
    while(i<collection.size()){
        int nextInc = Math.min(collection.size()-i,batchSize);
        List<T> batch = collection.subList(i,i+nextInc);
        batches.add(batch);
        i = i + nextInc;
    }

    return batches;
}

Bitte lassen Sie mich wissen, ob bereits ein Dienstprogramm für dasselbe vorhanden ist.

Harish
quelle
4
Ich bin mir nicht sicher, ob dies nicht zum Thema gehört. Die Frage ist nicht "welche Bibliothek macht das", sondern "wie kann ich das mit Apache Common Utils machen".
Florian F
@FlorianF Ich stimme dir zu. Diese Frage und ihre Antworten sind sehr nützlich und können mit einer kleinen Bearbeitung gespeichert werden. Es war eine faule Handlung, es hastig zu schließen.
Endery
Nützlichen Blog-Beitrag mit netter Klasse und Benchmarks hier gefunden: e.printstacktrace.blog/…
Benj

Antworten:

250

Check out von Google Guava : Lists.partition(java.util.List, int)

Gibt aufeinanderfolgende Unterlisten einer Liste mit derselben Größe zurück (die endgültige Liste kann kleiner sein). Wenn Sie beispielsweise eine Liste [a, b, c, d, e]mit einer Partitionsgröße von 3 partitionieren [[a, b, c], erhalten Sie: [d, e]]- eine äußere Liste mit zwei inneren Listen mit drei und zwei Elementen, alle in der ursprünglichen Reihenfolge.

Tomasz Nurkiewicz
quelle
Link partition documentation und Link code example
Austin Haws
16
Für gewöhnliche Apache-Benutzer ist die Funktion auch verfügbar: commons.apache.org/proper/commons-collections/apidocs/org/…
Xavier Portebois
3
Wenn Sie mit einer Liste arbeiten, verwende ich die Bibliothek "Apache Commons Collections 4". Es hat eine Partitionsmethode in der ListUtils-Klasse: ... int targetSize = 100; List <Integer> largeList = ... List <List <Integer>> output = ListUtils.partition (largeList, targetSize); Diese Methode wird von code.google.com/p/guava-libraries
Swapnil Jaju
1
Danke dir. Ich kann nicht glauben, wie schwer das in Java ist.
Onkel Long Hair
51

Wenn Sie einen Java-8-Stapelstrom erstellen möchten, können Sie den folgenden Code ausprobieren:

public static <T> Stream<List<T>> batches(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));
}

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);

    System.out.println("By 3:");
    batches(list, 3).forEach(System.out::println);

    System.out.println("By 4:");
    batches(list, 4).forEach(System.out::println);
}

Ausgabe:

By 3:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10, 11, 12]
[13, 14]
By 4:
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14]
Tagir Valeev
quelle
Wie kann ich diesen Ansatz unterbrechen, fortsetzen oder zurückkehren?
Miral
15

Ein anderer Ansatz besteht darin, Collectors.groupingByIndizes zu verwenden und die gruppierten Indizes dann den tatsächlichen Elementen zuzuordnen:

    final List<Integer> numbers = range(1, 12)
            .boxed()
            .collect(toList());
    System.out.println(numbers);

    final List<List<Integer>> groups = range(0, numbers.size())
            .boxed()
            .collect(groupingBy(index -> index / 4))
            .values()
            .stream()
            .map(indices -> indices
                    .stream()
                    .map(numbers::get)
                    .collect(toList()))
            .collect(toList());
    System.out.println(groups);

Ausgabe:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]

Adrian Bona
quelle
1
@Sebien Dies funktioniert für den allgemeinen Fall. Das groupingByist auf die Elemente der getan IntStream.range, nicht die Listenelemente. Siehe z . B. ideone.com/KYBc7h .
Radiodef
@MohammedElrashidy Sebien hat ihren Kommentar entfernt, Sie können jetzt Ihren entfernen.
Albert Hendriks
7

Ich habe mir das ausgedacht:

private static <T> List<List<T>> partition(Collection<T> members, int maxSize)
{
    List<List<T>> res = new ArrayList<>();

    List<T> internal = new ArrayList<>();

    for (T member : members)
    {
        internal.add(member);

        if (internal.size() == maxSize)
        {
            res.add(internal);
            internal = new ArrayList<>();
        }
    }
    if (internal.isEmpty() == false)
    {
        res.add(internal);
    }
    return res;
}
Raz Coren
quelle
6

Mit Java 9 können Sie IntStream.iterate()mit hasNextBedingung verwenden. So können Sie den Code Ihrer Methode folgendermaßen vereinfachen:

public static <T> List<List<T>> getBatches(List<T> collection, int batchSize) {
    return IntStream.iterate(0, i -> i < collection.size(), i -> i + batchSize)
            .mapToObj(i -> collection.subList(i, Math.min(i + batchSize, collection.size())))
            .collect(Collectors.toList());
}

Mit {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}wird das Ergebnis von getBatches(numbers, 4)sein:

[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]]
Samuel Philipp
quelle
5

Das folgende Beispiel zeigt das Aufteilen einer Liste:

package de.thomasdarimont.labs;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SplitIntoChunks {

    public static void main(String[] args) {

        List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);

        List<List<Integer>> chunks = chunk(ints, 4);

        System.out.printf("Ints:   %s%n", ints);
        System.out.printf("Chunks: %s%n", chunks);
    }

    public static <T> List<List<T>> chunk(List<T> input, int chunkSize) {

        int inputSize = input.size();
        int chunkCount = (int) Math.ceil(inputSize / (double) chunkSize);

        Map<Integer, List<T>> map = new HashMap<>(chunkCount);
        List<List<T>> chunks = new ArrayList<>(chunkCount);

        for (int i = 0; i < inputSize; i++) {

            map.computeIfAbsent(i / chunkSize, (ignore) -> {

                List<T> chunk = new ArrayList<>();
                chunks.add(chunk);
                return chunk;

            }).add(input.get(i));
        }

        return chunks;
    }
}

Ausgabe:

Ints:   [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Chunks: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
Thomas Darimont
quelle
4

Es gab eine andere Frage , die als Duplikat dieser Frage geschlossen wurde, aber wenn Sie sie genau lesen, ist sie auf subtile Weise anders. Wenn also jemand (wie ich) tatsächlich eine Liste in eine bestimmte Anzahl von fast gleich großen Unterlisten aufteilen möchte , lesen Sie weiter.

Ich habe den hier beschriebenen Algorithmus einfach nach Java portiert .

@Test
public void shouldPartitionListIntoAlmostEquallySizedSublists() {

    List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f", "g");
    int numberOfPartitions = 3;

    List<List<String>> split = IntStream.range(0, numberOfPartitions).boxed()
            .map(i -> list.subList(
                    partitionOffset(list.size(), numberOfPartitions, i),
                    partitionOffset(list.size(), numberOfPartitions, i + 1)))
            .collect(toList());

    assertThat(split, hasSize(numberOfPartitions));
    assertEquals(list.size(), split.stream().flatMap(Collection::stream).count());
    assertThat(split, hasItems(Arrays.asList("a", "b", "c"), Arrays.asList("d", "e"), Arrays.asList("f", "g")));
}

private static int partitionOffset(int length, int numberOfPartitions, int partitionIndex) {
    return partitionIndex * (length / numberOfPartitions) + Math.min(partitionIndex, length % numberOfPartitions);
}
Stefan Reisner
quelle
4

Verwenden Sie Apache Commons ListUtils.partition .

org.apache.commons.collections4.ListUtils.partition(final List<T> list, final int size)
Paul Rambags
quelle
3

Mit verschiedenen Cheats aus dem Internet kam ich zu dieser Lösung:

int[] count = new int[1];
final int CHUNK_SIZE = 500;
Map<Integer, List<Long>> chunkedUsers = users.stream().collect( Collectors.groupingBy( 
    user -> {
        count[0]++;
        return Math.floorDiv( count[0], CHUNK_SIZE );
    } )
);

Wir verwenden count, um einen normalen Sammlungsindex nachzuahmen.
Anschließend gruppieren wir die Sammlungselemente in Buckets, wobei wir den algebraischen Quotienten als Bucket-Nummer verwenden.
Die endgültige Karte enthält als Schlüssel die Bucket-Nummer und als Wert den Bucket selbst.

Sie können dann einfach eine Operation an jedem der Eimer durchführen mit:

chunkedUsers.values().forEach( ... );
Nicolas Nobelis
quelle
4
Könnte ein AtomicIntegerfor count verwenden.
jkschneider
1
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);
Yohann
quelle
1

Ähnlich wie OP ohne Streams und Bibliotheken, aber prägnanter:

public <T> List<List<T>> getBatches(List<T> collection, int batchSize) {
    List<List<T>> batches = new ArrayList<>();
    for (int i = 0; i < collection.size(); i += batchSize) {
        batches.add(collection.subList(i, Math.min(i + batchSize, collection.size())));
    }
    return batches;
}
Albert Hendriks
quelle
0

Ein anderer Ansatz, um dies zu lösen, Frage:

public class CollectionUtils {

    /**
    * Splits the collection into lists with given batch size
    * @param collection to split in to batches
    * @param batchsize size of the batch
    * @param <T> it maintains the input type to output type
    * @return nested list
    */
    public static <T> List<List<T>> makeBatch(Collection<T> collection, int batchsize) {

        List<List<T>> totalArrayList = new ArrayList<>();
        List<T> tempItems = new ArrayList<>();

        Iterator<T> iterator = collection.iterator();

        for (int i = 0; i < collection.size(); i++) {
            tempItems.add(iterator.next());
            if ((i+1) % batchsize == 0) {
                totalArrayList.add(tempItems);
                tempItems = new ArrayList<>();
            }
        }

        if (tempItems.size() > 0) {
            totalArrayList.add(tempItems);
        }

        return totalArrayList;
    }

}
Jurrian Fahner
quelle
0

Ein Einzeiler in Java 8 wäre:

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;

private static <T> Collection<List<T>> partition(List<T> xs, int size) {
    return IntStream.range(0, xs.size())
            .boxed()
            .collect(collectingAndThen(toMap(identity(), xs::get), Map::entrySet))
            .stream()
            .collect(groupingBy(x -> x.getKey() / size, mapping(Map.Entry::getValue, toList())))
            .values();

}
Ori Popowski
quelle
0

Hier ist eine einfache Lösung für Java 8+:

public static <T> Collection<List<T>> prepareChunks(List<T> inputList, int chunkSize) {
    AtomicInteger counter = new AtomicInteger();
    return inputList.stream().collect(Collectors.groupingBy(it -> counter.getAndIncrement() / chunkSize)).values();
}
aleastD
quelle
0

Sie können den folgenden Code verwenden, um den Stapel der Liste zu erhalten.

Iterable<List<T>> batchIds = Iterables.partition(list, batchSize);

Sie müssen die Google Guava-Bibliothek importieren, um den obigen Code verwenden zu können.

mukul28.03
quelle
-1

import com.google.common.collect.Lists;

List<List<T>> batches = Lists.partition(List<T>,batchSize)

Verwenden Sie Lists.partition (List, batchSize). Sie müssen Listsaus Google Common Package ( com.google.common.collect.Lists) importieren

Es wird eine Liste von List<T>mit und die Größe jedes Elements zurückgegeben, die Ihrer entspricht batchSize.

v87278
quelle
Sie können auch eine eigene subList(startIndex, endIndex)Methode zum Aufbrechen der Liste basierend auf dem erforderlichen Index verwenden.
V87278