¿Existe una utilidad Java común para dividir una lista en lotes?

140

Escribí una utilidad para dividir una lista en lotes de un tamaño determinado. Solo quería saber si ya hay alguna utilidad de apache commons para esto.

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

Avíseme si ya existe alguna utilidad para el mismo.

Harish
fuente
44
No estoy seguro de que esto esté fuera de tema. La pregunta no es "qué biblioteca hace esto" sino "cómo puedo hacer esto con las utilidades comunes de apache".
Florian F
@FlorianF Estoy de acuerdo contigo. Esta pregunta y sus respuestas son muy útiles, y podrían guardarse bien con una pequeña edición. Fue una acción perezosa cerrarlo apresuradamente.
Endery
Encontramos una publicación de blog útil con buena clase y puntos de referencia aquí: e.printstacktrace.blog/…
Benj

Respuestas:

249

Echa un vistazo desde Google Guava : Lists.partition(java.util.List, int)

Devuelve sublistas consecutivas de una lista, cada una del mismo tamaño (la lista final puede ser más pequeña). Por ejemplo, particionando una lista que contiene [a, b, c, d, e]un tamaño de partición de 3 rendimientos [[a, b, c], [d, e]]- una lista externa que contiene dos listas internas de tres y dos elementos, todo en el orden original.

Tomasz Nurkiewicz
fuente
enlace partition documentation y enlace code example
Austin Haws
16
Para los usuarios comunes de apache, la función también está disponible: commons.apache.org/proper/commons-collections/apidocs/org/…
Xavier Portebois
3
Si está trabajando con una lista, uso la biblioteca "Apache Commons Collections 4". Tiene un método de partición en la clase ListUtils: ... int targetSize = 100; List <Integer> largeList = ... List <List <Integer>> output = ListUtils.partition (largeList, targetSize); Este método está adaptado de code.google.com/p/guava-libraries
Swapnil Jaju
1
Gracias. No puedo creer lo difícil que es hacer esto en Java.
Tío Long Hair
51

En caso de que desee producir una secuencia de lotes Java-8, puede probar el siguiente código:

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

Salida:

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
fuente
¿Cómo rompo, continúo o regreso en este enfoque?
Miral
15

Otro enfoque es utilizar Collectors.groupingByíndices y luego asignar los índices agrupados a los elementos reales:

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

Salida:

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

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

Adrian Bona
fuente
1
@Sebien Esto funciona para el caso general. El groupingByse realiza en los elementos de IntStream.range, no en los elementos de la lista. Ver, por ejemplo, ideone.com/KYBc7h .
Radiodef
@MohammedElrashidy Sebien ha eliminado su comentario, ahora puede eliminar el suyo.
Albert Hendriks
7

Se me ocurrió esta:

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
fuente
6

Con Java 9 puede usar IntStream.iterate()con hasNextcondición. Para que pueda simplificar el código de su método a esto:

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

Usando {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, el resultado de getBatches(numbers, 4)será:

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

El siguiente ejemplo demuestra la fragmentación de una Lista:

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

Salida:

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
fuente
4

Hubo otra pregunta que se cerró como un duplicado de esta, pero si la lees detenidamente, es sutilmente diferente. Entonces, en caso de que alguien (como yo) realmente quiera dividir una lista en un número dado de sublistas de casi el mismo tamaño , leyendo.

Simplemente porté el algoritmo descrito aquí a 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);
}
Stefan Reisner
fuente
4

Utilice Apache Commons ListUtils.partition .

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

Usando varios trucos de la web, llegué a esta solución:

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

Usamos count para imitar un índice de colección normal.
Luego, agrupamos los elementos de la colección en cubos, usando el cociente algebraico como número de cubo.
El mapa final contiene como clave el número del cubo, como valor el depósito en sí.

Luego puede realizar fácilmente una operación en cada uno de los cubos con:

chunkedUsers.values().forEach( ... );
Nicolas Nobelis
fuente
44
Podría usar un AtomicIntegerpara contar.
jkschneider
1
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);
Yohann
fuente
1

Similar a OP sin streams y libs, pero conciser:

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
fuente
0

Otro enfoque para resolver esto, pregunta:

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
fuente
0

Una línea en Java 8 sería:

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
fuente
0

Aquí hay una solución simple para 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
fuente
0

Puede usar el siguiente código para obtener el lote de la lista.

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

Debe importar la biblioteca Google Guava para usar el código anterior.

mukul28.03
fuente
-1

import com.google.common.collect.Lists;

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

Use Lists.partition (List, batchSize). Necesita importar Listsdesde el paquete común de google ( com.google.common.collect.Lists)

Devolverá la Lista de List<T>con y el tamaño de cada elemento igual a su batchSize.

v87278
fuente
También puede usar su propio subList(startIndex, endIndex)método para romper la lista según el índice requerido.
v87278