Esiste un'utilità Java comune per suddividere un elenco in batch?


141

Mi sono scritto un programma di utilità per spezzare un elenco in lotti di dimensioni determinate. Volevo solo sapere se ci sono già util util apache per questo.

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

Per favore fatemi sapere se esiste già un'utilità esistente per lo stesso.


4
Non sono sicuro che sia fuori tema. La domanda non è "quale libreria fa questo" ma "come posso fare questo con i programmi di utilità comuni di Apache".
Florian F,

@FlorianF Sono d'accordo con te. Questa domanda e le sue risposte sono molto utili e potrebbero essere ben salvate con una piccola modifica. È stata un'azione pigra chiuderla in fretta.
Endery,

Risposte:


250

Dai un'occhiata a Google Guava : Lists.partition(java.util.List, int)

Restituisce elenchi consecutivi di un elenco, ciascuno delle stesse dimensioni (l'elenco finale può essere più piccolo). Ad esempio, partizionare un elenco contenente [a, b, c, d, e]con una dimensione della partizione di 3 rese [[a, b, c], [d, e]]- un elenco esterno contenente due elenchi interni di tre e due elementi, tutti nell'ordine originale.


link partition documentation and link code example
Austin Haws,

16
Per gli utenti comuni di apache, la funzione è disponibile anche: commons.apache.org/proper/commons-collections/apidocs/org/…
Xavier Portebois

3
Se stai lavorando con un elenco, utilizzo la libreria "Apache Commons Collections 4". Ha un metodo di partizione nella classe ListUtils: ... int targetSize = 100; Elenco <Integer> largeList = ... Elenco <List <Integer>> output = ListUtils.partition (largeList, targetSize); Questo metodo è adattato da code.google.com/p/guava-libraries
Swapnil Jaju

1
Grazie. Non riesco a credere quanto sia difficile fare questo in Java.
Zio capelli lunghi

51

Nel caso in cui si desideri produrre un flusso di batch Java-8, è possibile provare il codice seguente:

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

Produzione:

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]

Come posso interrompere, continuare o tornare in questo approccio?
Miral,

15

Un altro approccio consiste nell'utilizzare gli Collectors.groupingByindici e quindi mappare gli indici raggruppati sugli elementi reali:

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

Produzione:

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

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


1
@Sebien Questo funziona per il caso generale. Il groupingByavviene sugli elementi del IntStream.range, non gli elementi della lista. Vedi ad esempio ideone.com/KYBc7h .
Radiodef,

@MohammedElrashidy Sebien ha rimosso il suo commento, ora puoi rimuovere il tuo.
Albert Hendriks,

7

Ho pensato a questo:

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

Con Java 9 è possibile utilizzare IntStream.iterate()con hasNextcondizione. Quindi puoi semplificare il codice del tuo metodo in questo modo:

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

Utilizzando {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, il risultato di getBatches(numbers, 4)sarà:

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

5

L'esempio seguente mostra il blocco di un elenco:

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

Produzione:

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

C'era un'altra domanda che era stata chiusa come duplicata di questa, ma se la leggi attentamente, è leggermente diversa. Quindi nel caso qualcuno (come me) voglia davvero dividere un elenco in un determinato numero di elenchi di dimensioni quasi uguali , quindi continuate a leggere.

Ho semplicemente portato l'algoritmo descritto qui su Java.

@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

Usando vari cheat dal web, sono arrivato a questa soluzione:

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

Usiamo count per imitare un normale indice di raccolta.
Quindi, raggruppiamo gli elementi della raccolta in bucket, usando il quoziente algebrico come numero di bucket.
La mappa finale contiene come chiave il numero di bucket, come valore il bucket stesso.

È quindi possibile eseguire facilmente un'operazione su ciascuno dei secchi con:

chunkedUsers.values().forEach( ... );

4
Potrebbe usare un AtomicIntegerconteggio.
jkschneider,

1
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);

1

Simile a OP senza flussi e librerie, ma concisore:

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

Un altro approccio per risolvere questo problema, domanda:

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

Un one-liner in Java 8 sarebbe:

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

Ecco una semplice soluzione per 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

È possibile utilizzare il codice seguente per ottenere il batch dell'elenco.

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

Devi importare la libreria di Google Guava per utilizzare il codice sopra.


-1

import com.google.common.collect.Lists;

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

Usa Lists.partition (Elenco, batchSize). Devi importare Listsdal pacchetto comune di google ( com.google.common.collect.Lists)

Restituirà Elenco di List<T>con e la dimensione di ogni elemento uguale al tuo batchSize.


È inoltre possibile utilizzare il proprio subList(startIndex, endIndex)metodo per interrompere l'elenco in base all'indice richiesto.
v87278,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.