Deluxxx
Deluxxx

Reputation: 180

Mixing two lists alternately using stream

I implemented this using a loop, but I don't know how do that using a stream. We get two lists for example 1 2 3 and 4 5 6, after mixing the result will be 1 4 2 5 3 6.

public <T> List<T> mixingList(List<T> list1, List<T> list2) {
    List<T> result = new ArrayList<>();

    int maxSize = list1.size() > list2.size() ? list1.size() : list2.size();

    for (int i = 0; i < maxSize; i++) {
        if (i < list1.size()) {
            result.add(list1.get(i));
        }

        if (i < list2.size()) {
            result.add(list2.get(i));
        }
    }

    return result;
}

I prepare testing for that. There is 3 tests first - the same size the list second - with one empty list third - with the different size

@Test
public void shouldReturnShakedList() {
    //given
    List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 3, 5));

    List<Integer> list2 = new ArrayList<>(Arrays.asList(2, 4, 6));

    //when
    List<Integer> results = new ShakeListUtil().mixingList(list1, list2);

    //then
    Assertions.assertThat(results).containsExactly(1, 2, 3, 4, 5, 6);
    Assertions.assertThat(results).hasSize(6);
}

@Test
public void shouldReturnJustList2IfList1IsEmpty() {
    //given
    List<Integer> list1 = new ArrayList<>();

    List<Integer> list2 = new ArrayList<>(Arrays.asList(2, 4, 6));

    //when
    List<Integer> results = new ShakeListUtil().mixingList(list1, list2);

    //then
    Assertions.assertThat(results).containsExactly(2, 4, 6);
    Assertions.assertThat(results).hasSize(3);

}

@Test
public void shouldReturnShakedListIfTheSizeListIsDifferent() {
    //given
    List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 3));

    List<Integer> list2 = new ArrayList<>(Arrays.asList(2, 4, 6));

    //when
    List<Integer> results = new ShakeListUtil().mixingList(list1, list2);

    //then
    Assertions.assertThat(results).containsExactly(1, 2, 3, 4, 6);
    Assertions.assertThat(results).hasSize(5);
}

Any idea how do that on java streams?

Upvotes: 3

Views: 673

Answers (3)

Mureinik
Mureinik

Reputation: 311723

One approach could be to use an IntStream to get a stream of list indices, map them to Optionals based on whether the list contains this index or not, and then resolve them, although, to be honest, I'm not sure this is more elegant than your original approach:

public <T> List<T> mixingList(List<T> list1, List<T> list2) {
    int maxSize = Math.max(list1.size(), list2.size());

    return IntStream.range(0, maxSize)
            .mapToObj(i -> Stream.of(listIndexToOptional(list1, i), 
                                     listIndexToOptional(list2, i)))
            .flatMap(Function.identity())
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList());
}

private static <T> Optional<T> listIndexToOptional(List<T> list, int index) {
    return index < list.size() ? Optional.of(list.get(index)) : Optional.empty();
}

Upvotes: 1

Samuel Philipp
Samuel Philipp

Reputation: 11050

You can use split this into two parts. First you get the min numbers of both lists and mix both lists until this index. After that you append the remaining items in the larger list. To combine both you can use Stream.concat():

private static <T> List<T> mixingList(List<T> list1, List<T> list2) {
    int min = Math.min(list1.size(), list2.size());
    return Stream.concat(
            IntStream.range(0, min).boxed()
                    .flatMap(i -> Stream.of(list1.get(i), list2.get(i))),
            (list1.size() < list2.size() ? list2 : list1).stream().skip(min)
    ).collect(Collectors.toList());
}

Alternatively you can just use Stream.concat() while using Stream.flatMap():

private static <T> List<T> mixingList(List<T> list1, List<T> list2) {
    return IntStream.range(0, Math.max(list1.size(), list2.size())).boxed()
            .flatMap(i -> Stream.concat(
                    i < list1.size() ? Stream.of(list1.get(i)) : Stream.empty(),
                    i < list2.size() ? Stream.of(list2.get(i)) : Stream.empty()))
            .collect(Collectors.toList());
}

Upvotes: 1

Sweeper
Sweeper

Reputation: 272370

Here is a solution using streams:

public static <T> List<T> mixingList(List<T> list1, List<T> list2) {
    int shorter = Math.min(list1.size(), list2.size());
    int longer = Math.max(list1.size(), list2.size());
    Stream<T> firstPart = IntStream.range(0, shorter).mapToObj(x -> Stream.of(list1.get(x), list2.get(x))).flatMap(x -> x);
    if (longer > shorter) {
        Stream<T> secondPart = (list1.size() > list2.size() ? list1 : list2).subList(shorter, longer).stream();
        return Stream.concat(firstPart, secondPart).collect(Collectors.toList());
    } else {
        return firstPart.collect(Collectors.toList());
    }
}

The magic happens in mapToObj and flatMap. It maps every index to a stream of two list elements, one from each list given. And then it flattens the stream of streams with flatMap.

After that, if the two lists have different sizes, it gets the rest of the longer list and concatenates it to the end.

Upvotes: 1

Related Questions