How to join list of non-string objects using streams

Viewed 293

I have a list of Objects that I want to map to another list that is joined by another object of that type.

i.e:

List<Integer> list = List.of(5,6,7,8);
// is it possible to insert `1` between every item in the list?
// joined list = [5,1,6,1,7,1,8]

This is just an example, the list can be of any type not just Integer.

My use case is this: I have a list of objects of type X, and I need to insert a certain object between every 2 items of that list.

I know it can be easily done by a for-loop, but wondering if there's a solution using Stream.

4 Answers

You can just insert a 1 before each element, then skip the first element in the stream.

List<Integer> list2 = list.stream()
    .flatMap(i -> Stream.of(1, i))
    .skip(1)
    .collect(Collectors.toList());

As you've said you don't want to handle only Integers, have a look at this more generic solution:

public static <T> List<T> insertBetween(T t, List<T> list) {
    return list.stream()
        .flatMap(e -> Stream.of(t, e))
        .skip(1)
        .collect(Collectors.toList());
}

Which you can call like this:

List<Pojo> result = insertBetween(somePojo, pojos);

But keep in mind that if t is not immutable you can get rather unusual behaviour, as you're simply inserting a reference to t between each element.

You could overcome this by using a Supplier<T> s instead of directly T t. That way you could change the flatMap to:

.flatMap(e -> Stream.of(s.get(), e))

Which could then be called like this:

List<Pojo> result = insertBetween(() -> new Pojo(), pojos);

You could do this with a custom collector, although I doubt this will be any better than using straight-forward loops:

List<Integer> listWithOnes =
    list.stream().collect(
        Collector.of(
            ArrayList::new,
            (result, e) -> { 
                if (!result.isEmpty()) {
                    result.add(1);
                }
                result.add(e);
            },
            (left, right) -> {
                left.add(1); 
                left.addAll(right); 
                return left;
            }
        )
    );

Using flatMap is a solution for this:

Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.

In your case you can extend each element with an additional element, skipping first one:

List<Integer> list = List.of(5,6,7,8)
        .stream()
        .flatMap(element -> Stream.of(1, element))
        .skip(1)
        .collect(Collectors.toList());

System.out.println(list); // [5, 1, 6, 1, 7, 1, 8]

Similar to this

List<Integer> list = List.of(5,6,7,8);

List reduce = list.stream().reduce(new ArrayList<Integer>(), (List a, Integer v) -> {
    if (a.size() > 0)
        a.add(1);
    a.add(v);
    return a;
}, (a1, a2) -> a1);
Related