2015-03-26 33 views
9

Biorąc pod uwagę elementy java.util.List z n i żądany rozmiar strony m, chcę przekształcić go na mapę zawierającą elementy n/m+n%m. Każdy element mapy powinien zawierać elementy m.Jak dokonać paginacji listy obiektów w języku Java 8?

Oto przykład z liczb:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 

    // What is the equivalent Java 8 code to create the map below from my list? 

    Map<Integer, List<Integer>> map = new HashMap<>(); 
    map.put(0, Arrays.asList(1,2,3)); 
    map.put(1, Arrays.asList(4,5,6)); 
    map.put(2, Arrays.asList(7,8,9)); 
    map.put(3, Arrays.asList(10)); 

Czy to możliwe, przy użyciu języka Java 8?

+1

Co próbowałeś do tej pory? Przeczytaj [Jak zadać dobre pytanie?] (Http://stackoverflow.com/help/how-to-ask). – DavidPostill

+0

Tak więc zajrzałem do Collectors :: partitioningBy, ale to dzieli listę z danym predykatem. Zapytałem o to, ponieważ nie wiem, od czego zacząć w Javie 8, aby to osiągnąć. – adragomir

+1

@ user3030447 Czy jesteś pewien, że chcesz 'Map ' a nie 'Map >'? Zawsze możesz przekonwertować listę na ciąg znaków w fazie prezentacji ... –

Odpowiedz

7

Można użyć IntStream.iterate połączeniu z toMap kolektora i sposobu subList na List (dzięki Duncan dla uproszczenia).

import static java.util.stream.Collectors.toMap; 
import static java.lang.Math.min; 

... 

static Map<Integer, List<Integer>> partition(List<Integer> list, int pageSize) { 
    return IntStream.iterate(0, i -> i + pageSize) 
      .limit((list.size() + pageSize - 1)/pageSize) 
      .boxed() 
      .collect(toMap(i -> i/pageSize, 
         i -> list.subList(i, min(i + pageSize, list.size())))); 
} 

Najpierw należy obliczyć liczbę kluczy potrzebnych na mapie. Jest to podane przez (list.size() + pageSize - 1)/pageSize (będzie to limit strumienia).

Następnie tworzysz strumień, który tworzy sekwencję 0, pageSize, 2* pageSize, ....

Teraz dla każdej wartości i chwycić odpowiedni subList który będzie nasza wartość (trzeba dodatkowy czek na ostatniej subList nie otrzymania poza granicami), dla którego mapa odpowiedni klawisz, który będzie sekwencja 0/pageSize, pageSize/pageSize, 2*pageSize/pageSize że dzielisz przez pageSize, aby uzyskać naturalną sekwencję 0, 1, 2, ....

Rurociąg może być bezpiecznie uruchomiony równolegle (może być konieczne użycie kolektora toConcurrentMap). Jak skomentował Brian Goetz (dzięki za przypomnienie mi, że tak), iterate nie jest warta, jeśli chcesz zrównoleglić strumień, więc tutaj jest wersja z range.

return IntStream.range(0, (list.size() + pageSize - 1)/pageSize) 
       .boxed() 
       .collect(toMap(i -> i , 
           i -> list.subList(i * pageSize, min(pageSize * (i + 1), list.size())))); 

Tak jak z przykładu (lista 10 elementów o rozmiarze stronie 3), otrzymasz następującą sekwencję:

0, 3, 6, 9, 12, 15, ... że można ograniczyć do (10 + 3 - 1)/3 = 12/3 = 4, które pozwalają sekwencja 0, 3, 6, 9. Teraz każda wartość jest mapowany do jego odpowiedniego podmenu:

0/pageSize = 0 -> list.subList(0, min(0 + pageSize, 10)) = list.subList(0, 3); 
3/pageSize = 1 -> list.subList(3, min(3 + pageSize, 10)) = list.subList(3, 6); 
6/pageSize = 2 -> list.subList(6, min(6 + pageSize, 10)) = list.subList(6, 9); 
9/pageSize = 3 -> list.subList(9, min(9 + pageSize, 10)) = list.subList(6, 10); 
            ^
             | 
         this is the edge-case for the last sublist to 
         not be out of bounds 


Jeśli naprawdę chcesz Map<Integer, String> można zastąpić funkcję wartość odwzorowujący z

import static java.util.stream.Collectors.joining; 

... 

i -> list.subList(i, min(i + pageSize, list.size())) 
     .stream() 
     .map(Object::toString) 
     .collect(joining(",")) 

które po prostu zebrać elementy oddzielone przecinkiem w pojedynczy ciąg.

+0

To jest wynik {0 = [1, 2, 3], 1 = [4, 5, 6], 2 = [7, 8, 9], 3 = [10]} dla pierwszej wersji. Alexis, jesteś BLAST dziecko, dziękuję za pokazanie mi. Rozumiem kod i poszerzam swoją wiedzę :) Życzę wszystkiego najlepszego – adragomir

+1

@ user3030447 Dodałem, jak zachowuje się w twoim odpowiednim przykładzie. –

+2

Dobre rozwiązanie. Możesz uprościć swój limit do: '.limit ((list.size() + pageSize - 1)/pageSize)', ponieważ obie wartości są dodatnie (patrz [ta odpowiedź] (http://stackoverflow.com/a/7446742/474189)). Metoda odwzorowania wartości mogłaby również uprościć 'i -> list.subList (i, Math.min (i + pageSize, list.size()))' –

0

Jak zauważono w komentarzach, działa to również wtedy, gdy lista nie jest naturalną sekwencją liczb całkowitych. Będziesz musiał użyć wygenerowanego IntStream i odnieść się do elementów na liście według indeksu.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 

Map<Integer, String> map = IntStream 
    .range(0, list.size()) 
    .boxed() 
    .collect(groupingBy(
     i -> i/3, //no longer i-1 because we start with 0 
     mapping(i -> list.get((int) i).toString(), joining(",")) 
     )); 

//result: {0="1,2,3", 1="4,5,6", 2="7,8,9", 3="10"} 

Zaczynamy od IntStream reprezentujących indeksy na liście.

groupingBy grupuje elementy według niektórych klasyfikatorów. W twoim przypadku grupuje x elementy na stronę.

mapping stosuje funkcję odwzorowania do elementów i zbiera je później. Mapowanie jest konieczne, ponieważ joining akceptuje tylko CharSequence. joining samo łączy elementy za pomocą arbitralnego ogranicznika.

+0

To działa dobrze, jeśli masz naturalną sekwencję na liście, ale powiedzmy, że lista [1, -1, 5, 2] i rozmiar strony 2, nie dostaniesz pożądanych rezultatów. –

+0

@AlexisC. To prawda, ale w takim przypadku możesz użyć wygenerowanego 'IntStream' i odnieść się do oryginalnej listy według indeksu. Algorytm nie zmieniłby się jednak. – zeroflagL

+0

@zeroflagL Dlaczego nie zaktualizujesz odpowiedzi, aby to pokazać? –

2

Proste rozwiązanie za pomocą Guava: com.google.common.collect.Lists#partition:

List<List<Integer>> partition = Lists.partition(list, 3); //<- here 
    Map map = IntStream.range(0, partition.size()).boxed().collect(Collectors.toMap(
        Function.identity(), 
        i -> partition.get(i))); 
Powiązane problemy