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à des utilitaires Apache communs 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 y a un utilitaire existant déjà pour le même.
Découvrez Lists.partition(Java.util.List, int)
from Google Guava :
Renvoie des sous-listes consécutives d'une liste, chacune de la même taille (la liste finale peut être plus petite). Par exemple, le fait de 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.
Si vous souhaitez générer un flux de lots Java-8, 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);
}
Sortie:
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]
Une autre approche consiste à utiliser Collectors.groupingBy
of index, puis à mapper les index 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);
Sortie:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
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;
}
L'exemple suivant illustre le découpage 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;
}
}
Sortie:
Ints: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Chunks: [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
Avec Java 9, vous pouvez utiliser IntStream.iterate()
avec la condition hasNext
. Donc, vous pouvez 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]]
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 sous la forme clé le numéro du compartiment, sous forme de valeur le compartiment lui-même.
Vous pouvez ensuite facilement effectuer une opération sur chacun des seaux avec:
chunkedUsers.values().forEach( ... );
Il y avait une autre question qui était fermée comme étant une copie de celle-ci, mais si vous la lisez attentivement, elle est légèrement différente. Donc, au cas où quelqu'un (comme moi) souhaite scinder une liste en un nombre donné de sous-listes de taille presque égale, puis continuez votre lecture.
J'ai simplement porté l'algorithme décrit ici en 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);
}
Utilisez Apache Commons ListUtils.partition .
List<T> batch = collection.subList(i,i+nextInc);
->
List<T> batch = collection.subList(i, i = i + nextInc);
Une autre approche pour résoudre ce problème, la 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;
}
}
import com.google.common.collect.Lists;
List<List<T>> batches = Lists.partition(List<T>,batchSize)
Utilisez Lists.partition (List, batchSize). Vous devez importer Lists
à partir de Google Common Package (com.google.common.collect.Lists
)
Il retournera une liste de List<T>
avec et la taille de chaque élément égale à votre batchSize
.
Un one-line 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();
}