2

How to split a list into a given number of lists, taking the elements in order and distributing them to the sub-lists (so not partitioning the list)?

I would like to do this as "nice" as possible (using Java 8 features or Guava or something similar.

  • Example list: [1 2 3 4 5 6 7]
  • Should be split in 3 : [1 4 7] [2 5] [3 6]
  • Should be split in 2 : [1 3 5 7] [2 4 6]
Belun
  • 4,151
  • 7
  • 34
  • 51
  • How are the elements distributed to their respective partition? – Flown Oct 06 '16 at 15:36
  • @Flown 1st element to 1st sub-list, 2nd element to 2nd sub-list, 3rd element to 1st sub-list, etc (in the example of 2 sub-lists) ; just take them in order from the original list and distribute them in that order to the sub-lists – Belun Oct 06 '16 at 15:38

2 Answers2

11

If the source list supports efficient random access, like ArrayList does, you can use

IntStream.range(0, source.size()).boxed()
  .collect(groupingBy(i->i%listCount, LinkedHashMap::new, mapping(source::get, toList())));

e.g.

List<Integer> source=IntStream.range(0, 20).boxed().collect(toList());
System.out.println(source);
int listCount=5;

Map<Integer, List<Integer>> collect = IntStream.range(0, source.size()).boxed()
  .collect(groupingBy(i->i%listCount, LinkedHashMap::new, mapping(source::get, toList())));
// in case it really has to be a List:
List<List<Integer>> result=new ArrayList<>(collect.values());

result.forEach(System.out::println);
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[0, 5, 10, 15]
[1, 6, 11, 16]
[2, 7, 12, 17]
[3, 8, 13, 18]
[4, 9, 14, 19]
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I like this, I was struggling to think about how to keep a count inside the stream – Ash Oct 06 '16 at 16:15
  • You can even avoid boxing every integer from 0 to n by using the standard collect on `IntStream`, although it seems a bit overkill tho: `.collect(LinkedHashMap::new, (m, i) -> m.computeIfAbsent(i % size, k -> new ArrayList<>()).add(list.get(i)), (m1, m2) -> m2.forEach((k,v) -> m1.merge(k, v, (l1,l2) -> { l1.addAll(l2); return l1; })) );` – Alexis C. Oct 06 '16 at 16:16
  • 1
    @Alexis C: that’s what I wanted to avoid here. Especially the merge function. It’s a pity that `Map.mergeAll` has been forgotten… – Holger Oct 06 '16 at 16:18
  • 2
    @Ash French: there is no general clean way to get an element position in a stream. Using `IntStream.range` and mapping to the element is the canonical way for random access sources, but for other kinds of streams, you’re out of luck. – Holger Oct 06 '16 at 16:29
  • 1
    @Belun: collectors are designed to be combinable and while the nesting might not look as elegant as the chaining of operations, they are more powerful as they are *extensible* and all other terminal stream operations can be considered just special cases of them. In fact, with the Java 9 additions of `flatMapping` and `filtering`, almost every stream operation is covered by a matching collector, then combinable with other collectors, most notably `groupingBy`/`partitioningBy`… – Holger Oct 06 '16 at 18:02
0

Something like this could put all your lists into a map, then you just need to get the sub-lists out of the map

int count = 0;
Map<Integer, List<Integer>> mapLists = list.stream()
                            .peek(i -> count ++)
                            .collect(Collectors.groupingBy(i -> count % numOfSubLists))

Another way using Guava

https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Lists.html#partition(java.util.List,%20int)

List<List<Integer>> lists = Lists.partition(list, noOfPartitions);
Ash
  • 2,562
  • 11
  • 31
  • Guava Lists.partition - Returns consecutive sublists of a list. Not I want. I need them distributed the other way. The groupBy solution might work – Belun Oct 06 '16 at 15:54
  • regarding the peek solution : peek is for debugging not messing with data (maybe use map() instead, but that does not work with external mutable variable) – Belun Oct 06 '16 at 16:08
  • you might be able to get rid of peek, and just modify the count in the groupingBy by doing something like this `groupingBy(i -> ++count % numberOfSubLists)` – Ash Oct 06 '16 at 16:13