Existe-t-il un utilitaire Java commun pour diviser une liste en lots?

141

Je me suis écrit un utilitaire pour diviser une liste en lots de taille donnée. Je voulais juste savoir s'il existe déjà un utilitaire apache commun pour cela.

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

S'il vous plaît laissez-moi savoir s'il existe déjà un utilitaire existant pour le même.

Harish
la source
4
Pas sûr que ce soit hors sujet. La question n'est pas "quelle bibliothèque fait cela" mais "comment puis-je faire cela avec les utilitaires courants d'Apache".
Florian F
@FlorianF Je suis d'accord avec vous. Cette question et ses réponses sont très utiles, et elle pourrait bien être sauvegardée avec une petite modification. C'était une action paresseuse de le fermer à la hâte.
Endery le
J'ai
Benj

Réponses:

250

Vérifiez depuis Google Guava : Lists.partition(java.util.List, int)

Renvoie des sous-listes consécutives d'une liste, chacune de la même taille (la liste finale peut être plus petite). Par exemple, partitionner une liste contenant [a, b, c, d, e]avec une taille de partition de 3 donne [[a, b, c], [d, e]]- une liste externe contenant deux listes internes de trois et deux éléments, le tout dans l'ordre d'origine.

Tomasz Nurkiewicz
la source
lien partition documentation et lien code example
Austin Haws
16
Pour les utilisateurs communs d'Apache, la fonction est également disponible: commons.apache.org/proper/commons-collections/apidocs/org/…
Xavier Portebois
3
Si vous travaillez avec une liste, j'utilise la bibliothèque "Apache Commons Collections 4". Il a une méthode de partition dans la classe ListUtils: ... int targetSize = 100; List <Integer> largeList = ... List <List <Integer>> output = ListUtils.partition (largeList, targetSize); Cette méthode est adaptée de code.google.com/p/guava-libraries
Swapnil Jaju
1
Je vous remercie. Je ne peux pas croire à quel point c'est difficile à faire en Java.
Oncle Long Hair
51

Si vous souhaitez produire un flux Java-8 de lots, vous pouvez essayer le code suivant:

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

Production:

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
la source
Comment interrompre, continuer ou revenir dans cette approche?
Miral
15

Une autre approche consiste à utiliser Collectors.groupingBydes indices puis à mapper les indices groupés aux éléments réels:

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

Production:

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

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

Adrian Bona
la source
1
@Sebien Cela fonctionne pour le cas général. Le groupingByse fait sur les éléments du IntStream.range, pas sur les éléments de la liste. Voir par exemple ideone.com/KYBc7h .
Radiodef
@MohammedElrashidy Sebien a supprimé son commentaire, vous pouvez désormais supprimer le vôtre.
Albert Hendriks
7

Je suis venu avec celui-ci:

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
la source
6

Avec Java 9, vous pouvez utiliser IntStream.iterate()avec hasNextcondition. Vous pouvez donc simplifier le code de votre méthode à ceci:

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

En utilisant {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, le résultat de getBatches(numbers, 4)sera:

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

L'exemple suivant illustre la segmentation d'une 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;
    }
}

Production:

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
la source
4

Il y avait une autre question qui a été fermée comme étant un double de celle-ci, mais si vous la lisez attentivement, elle est subtilement différente. Donc, au cas où quelqu'un (comme moi) voudrait réellement diviser une liste en un nombre donné de sous-listes de taille presque égale - , lisez la suite.

J'ai simplement porté l'algorithme décrit ici sur 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
la source
4

Utilisez Apache Commons ListUtils.partition .

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

En utilisant diverses astuces du Web, je suis arrivé à cette solution:

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

Nous utilisons count pour imiter un index de collection normal.
Ensuite, nous regroupons les éléments de la collection dans des compartiments, en utilisant le quotient algébrique comme numéro de compartiment.
La carte finale contient comme clé le numéro de compartiment, comme valeur le compartiment lui-même.

Vous pouvez alors effectuer facilement une opération sur chacun des buckets avec:

chunkedUsers.values().forEach( ... );
Nicolas Nobelis
la source
4
Pourrait utiliser un AtomicIntegerpour compter.
jkschneider
1
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);
Yohann
la source
1

Similaire à OP sans flux ni bibliothèques, mais concis:

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
la source
0

Une autre approche pour résoudre ce problème, question:

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
la source
0

Un one-liner dans Java 8 serait:

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
la source
0

Voici une solution simple pour 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
la source
0

Vous pouvez utiliser le code ci-dessous pour obtenir le lot de liste.

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

Vous devez importer la bibliothèque Google Guava pour utiliser le code ci-dessus.

mukul28.03
la source
-1

import com.google.common.collect.Lists;

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

Utilisez Lists.partition (List, batchSize). Vous devez importer à Listspartir du package commun google (com.google.common.collect.Lists )

Il renverra List of List<T>with et la taille de chaque élément égale à votre batchSize.

v87278
la source
Vous pouvez également utiliser leur propre subList(startIndex, endIndex)méthode pour casser la liste en fonction de l'index requis.
v87278