How to use streams to find pairs of elements from two lists or array multiplication
Asked Answered
G

7

22

I have two lists of numbers and I'd like to find all possible pairs of numbers. For example, given the lists [1, 2, 3] and [3, 4] the result should be:

[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]

I know I can do this using a for loop but is there any more concise way to do it using Java 8 streams?

I tried the following, but I'm missing something as I'm getting List<Stream<int[]>> instead of List<int[]>.

public static void main(String[] args) {
    List<Integer> list1 = Arrays.asList(1, 2, 3);
    List<Integer> list2 = Arrays.asList(3, 4);
    List<int[]> pairs = list1.stream()
                             .map(i -> list2.stream()
                                            .map(j -> new int[]{i, j}))
                             .collect(Collectors.toList());
    pairs.forEach(i -> {
        System.out.println("{" + i[0] + "," + i[1] + "}");
    });
}
Gracielagracile answered 14/2, 2017 at 7:18 Comment(6)
https://mcmap.net/q/587780/-how-to-multiply-2-double-matrices-using-streamsRadcliff
This Worked on using flatMap()Gracielagracile
You probably want to look at something about Cartesian product. But in this case I'd recommend to have two for loops as it's going to be far more readable, than any java 8 stream magic.Annals
@Annals Yes, see #32132487 (might be duplicate...)Tame
Does this answer your question? How to get all possible combinations from two arrays?Buehrer
Duplicate: How do I generate combinations of two arrays?Buehrer
N
17

Use flatMap() method instead of map(), it will combine the streams into one. Refer : Difference Between map() and flatMap() and flatMap() example

Nievesniflheim answered 14/2, 2017 at 7:37 Comment(2)
Accepting this as a answer as it provides links also to get more information on flatMapGracielagracile
Bring that information here instead of hiding it behind linksElite
T
14

You just need to replace your first map() with flatMap().

Taveras answered 14/2, 2017 at 7:35 Comment(0)
D
14

Here is a solution using IntStream with two int arrays as the source instead of List<Integer>. I wanted to see if it was possible to solve this problem without boxing every int as an Integer.

int[] one = new int[]{1, 2, 3};
int[] two = new int[]{3, 4};
List<IntIntPair> list = new ArrayList<>();
IntStream.of(one).forEach(i ->
        IntStream.of(two).mapToObj(j -> PrimitiveTuples.pair(i, j)).forEach(list::add));
System.out.println(list);
// [1:3, 1:4, 2:3, 2:4, 3:3, 3:4]

Unfortunately, I couldn't use flatMap on IntStream as it returns an IntStream. There is no flatMapToObj currently on IntStream, which is what would be needed here. So I used forEach instead.

The IntIntPair and PrimitiveTuples classes I used from Eclipse Collections, as they made it simpler to just output the list as a string. You could use int[] as you have in your solution. The code would look as follows.

List<int[]> list = new ArrayList<>();
IntStream.of(one).forEach(i ->
        IntStream.of(two).mapToObj(j -> new int[]{i, j}).forEach(list::add));

In the 8.1 release of Eclipse Collections (to be released mid-March), there is now a flatCollect method on all primitive containers in the library which can be used to solve this problem. This essentially does what a flatMapToObj method on IntStream should do.

IntList a = IntLists.mutable.with(1, 2, 3);
IntList b = IntLists.mutable.with(3, 4);
List<IntIntPair> result =
        a.flatCollect(
                i -> b.collect(j -> PrimitiveTuples.pair(i, j)),
                Lists.mutable.empty());
System.out.println(result);
// [1:3, 1:4, 2:3, 2:4, 3:3, 3:4]

Update:

As pointed out in the comments by Boris the Spider, the forEach solution would not be thread-safe and would break if the IntStream was parallel. The following solution should work in serial or parallel. I'm glad this was pointed out, because I hadn't thought to do a mapToObj on the IntStream and then followed by a flatMap.

int[] one = new int[]{1, 2, 3};
int[] two = new int[]{3, 4};
List<int[]> list = IntStream.of(one).parallel()
        .mapToObj(i -> IntStream.of(two).mapToObj(j -> new int[]{i, j}))
        .flatMap(e -> e)
        .collect(Collectors.toList());
list.stream().map(e -> "{" + e[0] + "," + e[1] + "}").forEach(System.out::println);

Note: I am a committer for Eclipse Collections.

Dosh answered 14/2, 2017 at 9:22 Comment(0)
S
0

In this particular case using flatMap you can even bypass an array creation and make your code simpler like this:

list1.stream()
     .flatMap(i -> list2.stream().map(j -> "{" + i+ "," + j + "}"))
     .forEach(System.out::println);
Salmi answered 15/2, 2017 at 6:46 Comment(0)
N
0
//return pair of numbers
List<List<Integer>> pairs=numbers.stream()
        .flatMap(i -> numbers2.stream()
        .map(j -> Arrays.asList(i,j)))
        .collect(Collectors.toList());

pairs.stream().forEach(System.out::println);
Nectar answered 29/3, 2018 at 1:16 Comment(0)
T
0

You can generate a 2d list of possible combinations using map and reduce methods:

List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(3, 4);

List<List<Integer>> combinations = Stream.of(list1, list2)
        // represent each list element as a singleton list
        .map(list -> list.stream().map(Collections::singletonList)
                // Stream<List<List<Integer>>>
                .collect(Collectors.toList()))
        // intermediate output
        //[[1], [2], [3]]
        //[[3], [4]]
        .peek(System.out::println)
        // summation of pairs of inner lists
        .reduce((listA, listB) -> listA.stream()
                // combinations of inner lists
                .flatMap(inner1 -> listB.stream()
                        // merge two inner lists into one
                        .map(inner2 -> Stream.of(inner1, inner2)
                                .flatMap(List::stream)
                                .collect(Collectors.toList())))
                // list of combinations
                .collect(Collectors.toList()))
        // otherwise an empty list
        .orElse(Collections.emptyList());

// output
System.out.println(combinations);
// [[1, 3], [1, 4], [2, 3], [2, 4], [3, 3], [3, 4]]

See also: Generate all combinations from multiple lists

Trattoria answered 25/4, 2021 at 13:56 Comment(0)
A
0

Of course, you can create a Stream everytime in the Stream#flatMap but in terms of performance, it's very poor.

Instead, I'd advise you to choose for 's Stream#mapMulti and operator on a more declarative way

List<int[]> pairs = list1.stream()
                         .mapMulti((Integer left, Consumer<int[]> consumer) -> 
                             list2.forEach(right -> consumer.accept(new int[]{left, right})))
                         .toList();

Which produces the same result, without the overhead of Stream creation

{1,3}
{1,4}
{2,3}
{2,4}
{3,3}
{3,4}
Ataxia answered 25/4, 2021 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.