J'essaie de scinder une liste en une liste où chaque liste a une taille maximale de 4.
Je voudrais savoir comment il est possible de faire en utilisant lambdas.
Actuellement, je le fais comme suit:
List<List<Object>> listOfList = new ArrayList<>();
final int MAX_ROW_LENGTH = 4;
int startIndex =0;
while(startIndex <= listToSplit.size() )
{
int endIndex = ( ( startIndex+MAX_ROW_LENGTH ) < listToSplit.size() ) ? startIndex+MAX_ROW_LENGTH : listToSplit.size();
listOfList.add(new ArrayList<>(listToSplit.subList(startIndex, endIndex)));
startIndex = startIndex+MAX_ROW_LENGTH;
}
METTRE &AGRAVE; JOUR
Il semble qu'il n'y ait pas de moyen simple d'utiliser des lambdas pour scinder des listes. Bien que toutes les réponses soient très appréciées, elles constituent également un merveilleux exemple de cas où les lambdas ne simplifient pas les choses.
Essayez cette approche:
static <T> List<List<T>> listSplitter(List<T> incoming, int size) {
// add validation if needed
return incoming.stream()
.collect(Collector.of(
ArrayList::new,
(accumulator, item) -> {
if(accumulator.isEmpty()) {
accumulator.add(new ArrayList<>(singletonList(item)));
} else {
List<T> last = accumulator.get(accumulator.size() - 1);
if(last.size() == size) {
accumulator.add(new ArrayList<>(singletonList(item)));
} else {
last.add(item);
}
}
},
(li1, li2) -> {
li1.addAll(li2);
return li1;
}
));
}
System.out.println(
listSplitter(
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
4
)
);
Notez également que ce code pourrait être optimisé au lieu de:
new ArrayList<>(Collections.singletonList(item))
utiliser celui-ci:
List<List<T>> newList = new ArrayList<>(size);
newList.add(item);
return newList;
Si vous avez VRAIMENT besoin d'un lambda, vous pouvez le faire comme ceci. Sinon, les réponses précédentes sont meilleures.
List<List<Object>> lists = new ArrayList<>();
AtomicInteger counter = new AtomicInteger();
final int MAX_ROW_LENGTH = 4;
listToSplit.forEach(pO -> {
if(counter.getAndIncrement() % MAX_ROW_LENGTH == 0) {
lists.add(new ArrayList<>());
}
lists.get(lists.size()-1).add(pO);
});
Sûrement le dessous est suffisant
final List<List<Object>> listOfList = new ArrayList<>(
listToSplit.stream()
.collect(Collectors.groupingBy(el -> listToSplit.indexOf(el) / MAX_ROW_LENGTH))
.values()
);
Diffusez-le, collectez-le avec un groupe: cela donne une carte d'objet -> liste, extrait les valeurs de la carte et passe directement dans le constructeur de votre choix (map.values () donne une collection et non une liste).
Peut-être que vous pouvez utiliser quelque chose comme ça
BiFunction<List,Integer,List> splitter= (list2, count)->{
//temporary list of lists
List<List> listOfLists=new ArrayList<>();
//helper implicit recursive function
BiConsumer<Integer,BiConsumer> splitterHelper = (offset, func) -> {
if(list2.size()> offset+count){
listOfLists.add(list2.subList(offset,offset+count));
//implicit self call
func.accept(offset+count,func);
}
else if(list2.size()>offset){
listOfLists.add(list2.subList(offset,list2.size()));
//implicit self call
func.accept(offset+count,func);
}
};
//pass self reference
splitterHelper.accept(0,splitterHelper);
return listOfLists;
};
Exemple d'utilisation
List<Integer> list=new ArrayList<Integer>(){{
add(1);
add(2);
add(3);
add(4);
add(5);
add(6);
add(7);
add(8);
add(8);
}};
//calling splitter function
List listOfLists = splitter.apply(list, 3 /*max sublist size*/);
System.out.println(listOfLists);
Et par conséquent nous avons
[[1, 2, 3], [4, 5, 6], [7, 8, 8]]
L'exigence est un peu étrange, mais vous pourriez faire:
final int[] counter = new int[] {0};
List<List<Object>> listOfLists = in.stream()
.collect(Collectors.groupingBy( x -> counter[0]++ / MAX_ROW_LENGTH ))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
Vous pourriez probablement simplifier cela en utilisant la variante de groupingBy
qui prend un mapSupplier
lambda et en fournissant un SortedMap
. Cela devrait retourner une EntrySet
qui itère dans l'ordre. Je le laisse comme un exercice.
Ce que nous faisons ici est:
Map<Integer,Object>
en utilisant un compteur pour grouper. Le compteur est contenu dans un tableau à un seul élément car le lambda ne peut utiliser des variables locales que si elles sont final
.Integer
.Stream::map()
pour convertir le flux de Map.Entry<Integer,Object>
en un flux de valeurs Object
.Cela ne bénéficie d'aucune parallélisation "libre". Il y a une surcharge de mémoire dans la variable Map
intermédiaire. Ce n'est pas particulièrement facile à lire.
Cependant, je ne le ferais pas, juste pour utiliser un lambda. Je ferais quelque chose comme:
for(int i=0; i<in.size(); i += MAX_ROW_LENGTH) {
listOfList.add(
listToSplit.subList(i, Math.min(i + MAX_ROW_LENGTH, in.size());
}
(Vous avez une copie défensive new ArrayList<>(listToSplit.subList(...))
. Je ne l'ai pas dupliquée, car ce n'est pas toujours nécessaire - par exemple, si la liste d'entrée est non modifiable et que les listes de sortie ne sont pas censées être modifiables. Mais réinsérez-la si vous le souhaitez. besoin dans votre cas.)
Ce sera extrêmement rapide sur n'importe quelle liste en mémoire. Il est très peu probable que vous souhaitiez le paralléliser.
Vous pouvez également écrire votre propre implémentation (non modifiable) de List
qui donne une vue sur le List<Object>
sous-jacent:
public class PartitionedList<T> extends AbstractList<List<T>> {
private final List<T> source;
private final int sublistSize;
public PartitionedList(T source, int sublistSize) {
this.source = source;
this.sublistSize = sublistSize;
}
@Override
public int size() {
return source.size() / sublistSize;
}
@Override
public List<T> get(int index) {
int sourceIndex = index * sublistSize
return source.subList(sourceIndex,
Math.min(sourceIndex + sublistSize, source.size());
}
}
Encore une fois, c'est à vous de décider si vous voulez faire des copies défensives ici.
Ce sera un temps d'accès Big-O équivalent à la liste sous-jacente.