Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Streams - Grouping a stream of tuples

Edit:: I am rephrasing the question so that its more clear

I have written this code

    List<ImmutablePair<Integer, Integer>> list = new ArrayList<ImmutablePair<Integer, Integer>>();
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    Stream<ImmutablePair<Integer, Integer>> stream = list.stream();
    Map<Integer, Integer> result = stream.collect(Collectors.groupingBy(
       ImmutablePair::getLeft,
            Collectors.mapping(
                    ImmutablePair::getRight,
                    Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
            )                
    ));

I want to process this list using steams so that the output is a map which contains keys (1, 3), (2, 6), (3, 9)

So basically, we group on the left item of the tuple and then sum the right item.

This code does not compile and the compiler says that it cannot resolve getLeft, getRight methods.

Here are the error messages from the compiler

/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: method summingInt in class Collectors cannot be applied to given types;
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                      ^
  required: ToIntFunction<? super T#1>
  found: Comparator<Object>
  reason: no instance(s) of type variable(s) T#2,U exist so that Comparator<T#2> conforms to ToIntFunction<? super T#1>
  where T#1,T#2,U are type-variables:
    T#1 extends Object declared in method <T#1>summingInt(ToIntFunction<? super T#1>)
    T#2 extends Object declared in method <T#2,U>comparing(Function<? super T#2,? extends U>)
    U extends Comparable<? super U> declared in method <T#2,U>comparing(Function<? super T#2,? extends U>)
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: incompatible types: cannot infer type-variable(s) T,U
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                                                      ^
    (argument mismatch; invalid method reference
      no suitable method found for getRight(Object)
          method Pair.getRight() is not applicable
            (actual and formal argument lists differ in length)
          method ImmutablePair.getRight() is not applicable
            (actual and formal argument lists differ in length))
  where T,U are type-variables:
    T extends Object declared in method <T,U>comparing(Function<? super T,? extends U>)
    U extends Comparable<? super U> declared in method <T,U>comparing(Function<? super T,? extends U>)
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:229: error: invalid method reference
                            Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
                                                                       ^
  non-static method getRight() cannot be referenced from a static context
  where R is a type-variable:
    R extends Object declared in class ImmutablePair
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:228: error: invalid method reference
                            ImmutablePair::getRight,
                            ^
  non-static method getRight() cannot be referenced from a static context
  where R is a type-variable:
    R extends Object declared in class ImmutablePair
/Users/abhi/JavaProjects/MovieLambda2/src/main/java/com/abhi/MovieLambda2.java:226: error: invalid method reference
                    ImmutablePair::getLeft,
                    ^
  non-static method getLeft() cannot be referenced from a static context
  where L is a type-variable:
    L extends Object declared in class ImmutablePair
like image 654
Knows Not Much Avatar asked Sep 20 '15 14:09

Knows Not Much


2 Answers

Assuming your ImmutablePair class looks like this (this class might need to be static depending on whether the sample code was executed from within a main method):

class ImmutablePair<T,T2> {
    private final T left;
    private final T2 right;

    public ImmutablePair(T left, T2 right) {
        this.left = left;
        this.right = right;

    }

    public T getLeft() {
        return left;
    }

    public T2 getRight() {
        return right;
    }
}

This seems to work for me:

List<ImmutablePair<Integer, Integer>> list = new ArrayList<>();
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(1, 1));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(2, 2));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    list.add(new ImmutablePair(3, 3));
    Map<Integer, Integer> collect = list.stream()
        .collect(
            Collectors.groupingBy(
                ImmutablePair::getLeft,
                Collectors.summingInt(ImmutablePair::getRight)));

    System.out.println(collect);

And the result is:

{1=3, 2=6, 3=9}

The possible problem

The problem is in this section of code:

        Collectors.mapping(
                ImmutablePair::getRight,
                Collectors.summingInt(Comparator.comparing(ImmutablePair::getRight))
        )

The Collectors.mapping collector first adapts an object of type T into and object of type U and then runs a collector on type U. (That's the gist of the javadoc for me)

So what you're doing is something like:

  • Transform ImmutablePair into an Integer.
  • The 2nd parameter is now a Collector that expects an Integer type, but you are "using an ImmutablePair type". Additionally, the Comparator.compare() returns a Comparator and you're looking to return an Integer type. So to fix your exact line of code is to change it to something like:

Collectors.mapping(ImmutablePair::getRight, Collectors.summingInt(Integer::valueOf))

But you don't need this because you can simplify this by calling Collectors.summingInt directly:

Collectors.summingInt(ImmutablePair::getRight)

like image 60
Shiraaz.M Avatar answered Sep 24 '22 04:09

Shiraaz.M


Update: this answers an original question (revision 2). New question is well answered by @Shiraaz.M.


Your Comparator accepts a pair, thus you need to call getRight with Collectors.mapping:

Map<String, Optional<Movie>> map = ml.getDataAsStream()
    .<ImmutablePair<String, Movie>>flatMap(x -> x.getGenre()
            .map(g -> new ImmutablePair<String, Movie>(g, x)))
    .collect(
            Collectors.groupingBy(
                ImmutablePair::getLeft,
                Collectors.mapping(ImmutablePair::getRight, 
                    Collectors.maxBy(Comparator.comparing(Movie::getVoteCount)))
            ));

Note that depending on compiler used you may need to specify some types explicitly as automatic inference may fail.

Actually such scenarios are well supported in my StreamEx library which enhances standard Java Streams. The same result can be achieved writing this:

Map<String, Optional<Movie>> map = StreamEx.of(ml.getDataAsStream())
    .cross(Movie::getGenre) // create (Movie, Genre) entries
    .invert() // swap entries to get (Genre, Movie)
    .grouping(Collectors.maxBy(Comparator.comparing(Movie::getVoteCount)));

Finally note that using maxBy collector you'll have Optional values which is useless as they cannot be empty. For maxBy/minBy scenarios it's better to use toMap collector with custom merging function:

Map<String, Movie> map = ml.getDataAsStream()
    .<ImmutablePair<String, Movie>>flatMap(x -> x.getGenre()
            .map(g -> new ImmutablePair<String, Movie>(g, x)))
    .collect(Collectors.toMap(ImmutablePair::getLeft, ImmutablePair::getRight, 
            BinaryOperator.maxBy(Comparator.comparing(Movie::getVoteCount))));

Or using StreamEx:

Map<String, Movie> map = StreamEx.of(ml.getDataAsStream())
    .cross(Movie::getGenre)
    .invert()
    .toMap(BinaryOperator.maxBy(Comparator.comparing(Movie::getVoteCount)));
like image 22
Tagir Valeev Avatar answered Sep 25 '22 04:09

Tagir Valeev