Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interleave elements in a stream with separator

Is there a nice way to use Java streams to interleave elements in a stream with a separator of the same type?

// Expected result in is list: [1, 0, 2, 0, 3]
List<Integer> is = Stream.of(1, 2, 3).intersperse(0).collect(toList()); 

This is similar to the intersperse function in Haskell and other functional languages.

I have seen many examples of how to join strings in a similar way but have not found any solutions for general lists.

like image 823
Lii Avatar asked Sep 02 '14 13:09

Lii


2 Answers

You can do it with flatMap, but you'll get an additional separator after the last element :

List<Integer> is = IntStream.of(1, 2, 3)
                            .flatMap(i -> IntStream.of(i, 0))
                            .collect(toList());

Here's another way, without the trailing separator :

List<Integer> is = IntStream.of(1, 2, 3)
                            .flatMap(i -> IntStream.of(0, i))
                            .skip(1)
                            .collect(toList());

This time we add the separator before each original element, and get rid of the leading separator.

like image 57
Eran Avatar answered Oct 22 '22 18:10

Eran


You can do a similar thing which Collectors.joining does with Strings with a little helper method:

static <T> Collector<T,List<T>,List<T>> intersperse(T delim) {
    return Collector.of(ArrayList::new, (l,e)-> {
        if(!l.isEmpty()) l.add(delim);
        l.add(e);
    }, (l,l2)-> {
        if(!l.isEmpty()) l.add(delim);
        l.addAll(l2);
        return l;
    });

then you can use it similar to Collectors.joining(delimiter):

List<Integer> l=Stream.of(1, 2, 3).collect(intersperse(0));

produces

[1, 0, 2, 0, 3]

Note that this is thread safe due to the way collect guards these operations.


If you don’t want to collect into a list but insert the delimiters as an intermediate stream operation you can do it this way:

Integer delimiter=0;
Stream.of(1, 2, 3)/** or any other stream source */
      .map(Stream::of)
      .reduce((x,y)->Stream.concat(x, Stream.concat(Stream.of(delimiter), y)))
      .orElse(Stream.empty())
      /* arbitrary stream operations may follow */
like image 23
Holger Avatar answered Oct 22 '22 17:10

Holger