How to paginate a list of objects in Java 8?

后端 未结 3 1392
离开以前
离开以前 2020-12-05 05:50

Given a java.util.List with n elements and a desired page size m, I want to transform it to a map containing n/m+n%m elem

相关标签:
3条回答
  • 2020-12-05 06:07

    As noted in the comments this also works if the list is not a natural sequence of integers. You would have to use a generated IntStream then and refer to the elements in list by index.

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

    We start with an IntStream representing the indices of the list.

    groupingBy groups the elements by some classifier. In your case it groups x elements per page.

    mapping applies a mapping function to the elements and collects them afterwards. The mapping is necessary because joiningonly accepts CharSequence. joining itself joins the elements by using an arbitrary delimiter.

    0 讨论(0)
  • 2020-12-05 06:08

    You could use IntStream.iterate combined with the toMap collector and the subList method on List (thanks to Duncan for the simplifications).

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

    You first calculate the numbers of keys you need in the map. This is given by (list.size() + pageSize - 1) / pageSize (this will be the limit of the stream).

    Then you create a Stream that creates the sequence 0, pageSize, 2* pageSize, ....

    Now for each value i you grab the corresponding subList which will be our value (you need an additional check for the last subList for not getting out of bounds) for which you map the corresponding key which will be the sequence 0/pageSize, pageSize/pageSize, 2*pageSize/pageSize that you divide by pageSize to get the natural sequence 0, 1, 2, ....

    The pipeline can be safely run in parallel (you may need to use the toConcurrentMap collector instead). As Brian Goetz commented (thanks for reminding me that), iterate is not worth if you want to parallelize the stream, so here's a version with 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()))));
    

    So as with your example (a list of 10 elements with a page size of 3), you'll get the following sequence:

    0, 3, 6, 9, 12, 15, ... that you limit to (10 + 3 - 1) / 3 = 12 / 3 = 4, which let the sequence 0, 3, 6, 9. Now each value is mapped to its corresponding sublist:

    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
    


    If you really want a Map<Integer, String> you could replace the value mapper function with

    import static java.util.stream.Collectors.joining;
    
    ...
    
    i -> list.subList(i, min(i + pageSize, list.size()))
             .stream()
             .map(Object::toString)
             .collect(joining(","))
    

    which just collect the elements separated by a comma into a single String.

    0 讨论(0)
  • 2020-12-05 06:14

    Simple solution using 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)));
    
    0 讨论(0)
提交回复
热议问题