Existe um utilitário Java comum para dividir uma lista em lotes?

140

Eu mesmo escrevi um utilitário para dividir uma lista em lotes de determinado tamanho. Eu só queria saber se já existe algum utilitário apache commons para isso.

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

Por favor, deixe-me saber se já existe algum utilitário para o mesmo.

Harish
fonte
4
Não tenho certeza se isso está fora do tópico. A questão não é "que biblioteca faz isso", mas "como posso fazer isso com os utilitários comuns do apache".
Florian F
@FlorianF Eu concordo com você. Esta pergunta e suas respostas são muito úteis e podem ser salvas com uma pequena edição. Foi uma ação preguiçosa fechá-la às pressas.
Endery
Encontrei postagens úteis no blog com boa classe e referências aqui: e.printstacktrace.blog/…
Benj

Respostas:

249

Confira a partir Google Guava : Lists.partition(java.util.List, int)

Retorna sublistas consecutivas de uma lista, cada uma do mesmo tamanho (a lista final pode ser menor). Por exemplo, o particionamento de uma lista contendo [a, b, c, d, e]um tamanho de partição de 3 produz [[a, b, c]: [d, e]]- uma lista externa contendo duas listas internas de três e dois elementos, todos na ordem original.

Tomasz Nurkiewicz
fonte
ligação partition documentation e ligação code example
Austin Haws
16
Para o Apache usuários comuns, a função também está disponível: commons.apache.org/proper/commons-collections/apidocs/org/...
Xavier Portebois
3
Se você estiver trabalhando com uma lista, uso a biblioteca "Apache Commons Collections 4". Possui um método de partição na classe ListUtils: ... int targetSize = 100; List <Integer> largeList = ... Listar <List <Integer>> output = ListUtils.partition (largeList, targetSize); Este método é adaptado de code.google.com/p/guava-libraries
Swapnil Jaju
1
Obrigado. Não acredito como isso é difícil em Java.
Tio Cabelo Comprido
51

Caso você queira produzir um fluxo de lotes Java-8, tente o seguinte 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);
}

Resultado:

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
fonte
Como faço para interromper, continuar ou retornar nessa abordagem?
Miral
15

Outra abordagem é usar Collectors.groupingByíndices e, em seguida, mapear os índices agrupados para os elementos reais:

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

Resultado:

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

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

Adrian Bona
fonte
1
@Sebien Isso funciona para o caso geral. O groupingByé feito nos elementos do IntStream.range, não nos elementos da lista. Veja, por exemplo, ideone.com/KYBc7h .
Radiodef 20/08/19
@MohammedElrashidy Sebien removeu o comentário, agora você pode remover o seu.
Albert Hendriks
7

Eu vim com este:

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

Com o Java 9, você pode usar IntStream.iterate()com hasNextcondição. Então você pode simplificar o código do seu método para isso:

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}, o resultado de getBatches(numbers, 4)será:

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

O exemplo a seguir demonstra a divisão de uma 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;
    }
}

Resultado:

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

Havia outra pergunta que foi encerrada como duplicada, mas se você ler de perto, é sutilmente diferente. Portanto, caso alguém (como eu) realmente queira dividir uma lista em um determinado número de sublistas de tamanho quase igual e depois continue lendo.

Eu simplesmente portava o algoritmo descrito aqui para 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
fonte
4

Use o Apache Commons ListUtils.partition .

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

Usando vários truques da web, cheguei a esta solução:

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 um índice de coleção normal.
Em seguida, agrupamos os elementos de coleção em buckets, usando o quociente algébrico como número do bucket.
O mapa final contém como chave o número do depósito, como valor o próprio depósito.

Você pode facilmente executar uma operação em cada um dos buckets com:

chunkedUsers.values().forEach( ... );
Nicolas Nobelis
fonte
4
Pode usar um AtomicIntegerpara contagem.
jkschneider
1
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);
Yohann
fonte
1

Semelhante ao OP sem fluxos e bibliotecas, mas conciso:

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

Outra abordagem para resolver isso, pergunta:

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

Um one-liner no Java 8 seria:

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

Aqui está uma solução simples 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
fonte
0

Você pode usar o código abaixo para obter o lote da lista.

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

Você precisa importar a biblioteca do Google Guava para usar o código acima.

mukul28.03
fonte
-1

import com.google.common.collect.Lists;

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

Use Lists.partition (List, batchSize). Você precisa importar Listsdo pacote comum do google (com.google.common.collect.Lists )

Ele retornará a lista de List<T>com e o tamanho de cada elemento igual ao seu batchSize.

v87278
fonte
Você também pode usar seu próprio subList(startIndex, endIndex)método para quebrar a lista com base no índice necessário.
v87278 18/06/19