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] + "}");
    });
}
Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
Ankur Singh
  • 339
  • 3
  • 16
  • http://stackoverflow.com/a/34784924/1849366 – Rajind Ruparathna Feb 14 '17 at 07:31
  • This Worked on using flatMap() – Ankur Singh Feb 14 '17 at 07:40
  • 3
    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. – evgenii Feb 14 '17 at 07:41
  • @evgenii Yes, see http://stackoverflow.com/questions/32131987/how-can-i-make-cartesian-product-with-java-8-streams (might be duplicate...) – Marco13 Feb 14 '17 at 13:42
  • 1
    Does this answer your question? [How to get all possible combinations from two arrays?](https://stackoverflow.com/questions/53780951/how-to-get-all-possible-combinations-from-two-arrays) –  Apr 17 '21 at 09:28
  • 1
    Duplicate: [How do I generate combinations of two arrays?](https://stackoverflow.com/questions/65169699/how-do-i-generate-combinations-of-two-arrays) –  Apr 17 '21 at 09:32

7 Answers7

17

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

Community
  • 1
  • 1
Rohit Gulati
  • 542
  • 3
  • 15
14

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

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
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.

Donald Raab
  • 6,458
  • 2
  • 36
  • 44
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);
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
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);
Pang
  • 9,564
  • 146
  • 81
  • 122
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

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}
Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89