webentwicklung-frage-antwort-db.com.de

Java 8: So verwandeln Sie eine Liste in eine Liste mit Lambda

Ich versuche, eine Liste in eine Liste zu unterteilen, wobei jede Liste eine maximale Größe von 4 hat.

Ich würde gerne wissen, wie dies mit Lambdas möglich ist.

Momentan ist es wie folgt:

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

UPDATE

Es scheint, dass es keine einfache Möglichkeit gibt, Lambdas zum Teilen von Listen zu verwenden. Obwohl alle Antworten sehr geschätzt werden, sind sie auch ein wunderbares Beispiel dafür, wenn Lambdas die Dinge nicht vereinfachen.

7
Lucas T

Versuchen Sie diesen Ansatz:

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

Beachten Sie auch, dass dieser Code anstelle von Folgendes optimiert werden könnte:

new ArrayList<>(Collections.singletonList(item))

verwende das hier:

List<List<T>> newList = new ArrayList<>(size);
newList.add(item);
return newList;
5
alex.b

Wenn Sie wirklich ein Lambda brauchen, kann es so gemacht werden. Ansonsten sind die vorherigen Antworten besser.

    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

Sicherlich reicht das unten aus

final List<List<Object>> listOfList = new ArrayList<>(
            listToSplit.stream()
                    .collect(Collectors.groupingBy(el -> listToSplit.indexOf(el) / MAX_ROW_LENGTH))
                    .values()
    );

Streame es, sammle es mit einer Gruppierung: Dies ergibt eine Map of Object -> List, zieht die Werte der Map und leitet sie direkt in den Konstruktor ein (map.values ​​() gibt eine Collection und keine List).

2
tom01

Vielleicht kannst du so etwas verwenden 

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

Verwendungsbeispiel

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

Und als Ergebnis haben wir 

[[1, 2, 3], [4, 5, 6], [7, 8, 8]]
2

Die Anforderung ist etwas seltsam, aber Sie könnten Folgendes tun:

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

Sie könnten dies möglicherweise rationalisieren, indem Sie die Variante von groupingByverwenden, für die ein mapSupplierlambda erforderlich ist, und einen SortedMapangeben. Dies sollte einen EntrySetzurückgeben, der in der Reihenfolge durchlaufen wird. Ich lasse es als Übung.

Was wir hier machen, ist:

  • Sammeln Ihrer Listenelemente in einem Map<Integer,Object> mithilfe eines Zählers zur Gruppe. Der Zähler wird in einem Einzelelement-Array gehalten, da das Lambda lokale Variablen nur verwenden kann, wenn sie finalsind.
  • Abrufen der Karteneinträge als Stream und Sortieren nach dem Schlüssel Integername__.
  • Verwenden Sie Stream::map(), um den Stream von Map.Entry<Integer,Object> in einen Stream mit ObjectWerten zu konvertieren.
  • Sammeln Sie dies in einer Liste.

Dies profitiert nicht von einer "freien" Parallelisierung. In der Zwischenvariable Maphat es einen Speicheraufwand. Es ist nicht besonders leicht zu lesen.


Aber ich würde das nicht tun, nur um einen Lambda zu benutzen. Ich würde so etwas machen:

for(int i=0; i<in.size(); i += MAX_ROW_LENGTH) {
    listOfList.add(
        listToSplit.subList(i, Math.min(i + MAX_ROW_LENGTH, in.size());
}

(Ihre hatte eine defensive Kopie new ArrayList<>(listToSplit.subList(...)). Ich habe sie nicht dupliziert, weil sie nicht immer notwendig ist - zum Beispiel, wenn die Eingabeliste nicht veränderbar ist und die Ausgabelisten nicht veränderbar sein sollen. Setzen Sie sie aber wieder ein, wenn Sie sich entscheiden brauche es in deinem Fall.)

Dies ist auf jeder In-Memory-Liste extrem schnell. Es ist sehr unwahrscheinlich, dass Sie es parallelisieren möchten.


Alternativ können Sie Ihre eigene (nicht veränderbare) Implementierung von Listschreiben, die eine Ansicht über den zugrunde liegenden List<Object> darstellt:

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

Auch hier liegt es an Ihnen, ob Sie hier defensive Kopien machen wollen.

Dies wird eine entsprechende Big-O-Zugriffszeit für die zugrunde liegende Liste haben.

2
slim