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.


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.


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]

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


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

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

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

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


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

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

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

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

}

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

}

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

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.


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.