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.


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.


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]

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]]


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

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]]

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]]

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


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

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

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

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

}

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

}

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

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.


-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.


Sie können auch eine eigene subList(startIndex, endIndex)Methode zum Aufbrechen der Liste basierend auf dem erforderlichen Index verwenden.
V87278
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.