21

I am trying to learn java - stream. I am able to do simple iteration / filter / map / collection etc.

When I was kind of trying to collect every 3 elements and print as shown here in this example, [collect every 3 elements and print and so on...]

    List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");

    int count=0;
    String append="";
    for(String l: list){
        if(count>2){
            System.out.println(append);
            System.out.println("-------------------");
            append="";
            count=0;
        }
        append = append + l;
        count++;
    }
    System.out.println(append);

output:

abc
-------------------
def
-------------------
ghi
-------------------
j

I am not getting any clue how to do this using stream. Should i implement my own collector to achieve this?

KitKarson
  • 5,211
  • 10
  • 51
  • 73
  • 1
    Streams are the wrong approach to do that. When working with streams you are supposed to do *stateless operations* on each element so that they can be executed in parallel and/or in any order if needed. The counter for the elements however *is* a state. You should better use regular `for` loop. – Timothy Truckle Mar 27 '17 at 22:51
  • 1
    Also, you can use `Iterables.partition` from Guava. – ZhekaKozlov Mar 28 '17 at 01:27
  • 3
    http://stackoverflow.com/a/28211518/2711488 – Holger Mar 28 '17 at 09:40
  • Possible duplicate of [Split list into multiple lists with fixed number of elements in java 8](http://stackoverflow.com/questions/28210775/split-list-into-multiple-lists-with-fixed-number-of-elements-in-java-8) – 4castle Mar 28 '17 at 17:36

6 Answers6

27

You can actually use an IntStream to simulate your list's pagination.

List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");

int pageSize = 3;

IntStream.range(0, (list.size() + pageSize - 1) / pageSize)
        .mapToObj(i -> list.subList(i * pageSize, Math.min(pageSize * (i + 1), list.size())))
        .forEach(System.out::println);

which outputs:

[a, b, c]
[d, e, f]
[g, h, i]
[j]

If you want to generate Strings, you can use String.join since you are dealing with a List<String> directly:

.mapToObj(i -> String.join("", list.subList(i * pageSize, Math.min(pageSize * (i + 1), list.size()))))
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • Cool... i should have known about sublist – KitKarson Mar 28 '17 at 14:23
  • 1
    *FYI:* This is only a good answer if input is an array-based `List` (e.g. `ArrayList` or `Arrays.asList()`). For `LinkedList` and other `Collection` objects, where `get(int index)` is not _O(1)_, performance will suffer. --- Also not usable if input is a Stream, which is one interpretation of question: *How to collect every N elements from a Stream*. --- Still, +1 for useful answer. – Andreas Apr 05 '17 at 23:17
  • @Andreas True, I'm just again linking here [the solution provided by Holger](http://stackoverflow.com/questions/28210775/split-list-into-multiple-lists-with-fixed-number-of-elements-in-java-8/28211518#28211518) if the source is a stream – Alexis C. Apr 06 '17 at 08:41
12

If you have Guava in your project, you can use Iterables.partition method:

import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
...

Stream<List<String>> stream = Streams.stream(Iterables.partition(list, 3));
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
  • I do not use it. I could. ll try – KitKarson Mar 28 '17 at 14:24
  • 1
    Good easy solution if potential extra jar is an option and input is an `Iterable` (most often the case), but not usable if input is a stream, which is one interpretation of question: *How to collect every N elements from a Stream*. Still, +1 for useful answer. – Andreas Apr 05 '17 at 23:16
11

You can create your own Collector. The easiest way is to call Collector.of().

Since your use case requires values to be processed in order, here is an implementation that simply doesn't support parallel processing.

public static Collector<String, List<List<String>>, List<List<String>>> blockCollector(int blockSize) {
    return Collector.of(
            ArrayList<List<String>>::new,
            (list, value) -> {
                List<String> block = (list.isEmpty() ? null : list.get(list.size() - 1));
                if (block == null || block.size() == blockSize)
                    list.add(block = new ArrayList<>(blockSize));
                block.add(value);
            },
            (r1, r2) -> { throw new UnsupportedOperationException("Parallel processing not supported"); }
    );
}

Test

List<String> input = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
List<List<String>> output = input.stream().collect(blockCollector(3));
output.forEach(System.out::println);

Output

[a, b, c]
[d, e, f]
[g, h, i]
[j]
Andreas
  • 154,647
  • 11
  • 152
  • 247
6

I solved it like this:

    List<String> list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
    int groupBy = 3;

    AtomicInteger index = new AtomicInteger(0);         
    Map<Integer, List<String>> groups = list.stream()
        .collect(Collectors.groupingBy(cdm -> index.getAndIncrement()/groupBy));

    System.out.println(groups);

It prepares a map where the line number is the key and the strings on the line are in the key.

BuckBazooka
  • 881
  • 1
  • 10
  • 18
3

I think the bets approach is using an amazing library StreamEx of Tagir Valeev. The solution fits in one line ))

StreamEx.ofSubLists(list, 3).toList();
Cepr0
  • 28,144
  • 8
  • 75
  • 101
1

The most obvious solution:

IntStream.range(0, list.size() / N)
         .map(i -> i * charactersAmount)
         .mapToObj(i -> list.subList(i, i + charactersAmount)
         .collect(Collectors.toWhateverYouWant());

The first line - you will get a stream of ints from 0 to amount of resulting lines. From your example, list.size() / N equals 4, so stream will be 0-1-2-3.

The second line - this stream will be mapped to the scaled by the charactersAmount one, in your case it is 3 - 0-3-6-9.

The third line will cut sublists out of your initial list.

The last line just treats the resulting stream as collection