web-dev-qa-db-fra.com

Java 8: comment transformer une liste en liste de listes à l'aide de lambda

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.

7
Lucas T

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;
5
alex.b

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

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

2
tom01

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]]
2
Vladyslav Nikolaiev

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:

  • Rassembler vos éléments de liste dans un 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.
  • Obtenir les entrées de carte sous forme de flux et les trier à l'aide de la touche Integer.
  • Utilisation de Stream::map() pour convertir le flux de Map.Entry<Integer,Object> en un flux de valeurs Object.
  • Recueillir cela dans une liste.

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.

2
slim