7

I would like to put the frequencies of the numbers in a TreeMap with the frequencies as the keys and the numbers that have that frequency in an ArrayList.

I have two problems:

1) I'm getting a "non-static methods cannot be referenced from a static context" error in the first parameter (AFAIK the stream references an object - what's going on?)

2) There are 4 parameters for Collectors.toMap() - it seems like parameter 4 requires initialization with a new TreeMap>, parameter 2 could be an ArrayList add() function and parameter 3 could be null (maybe). How is this done?

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {

        List<Integer> array = Arrays.asList(1, 2, 4, 5, 6, 4, 8, 4, 2, 3);

        Map<Integer, Long> m = array.stream()
            .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

        System.out.println(m);

        TreeMap<Long, List<Integer>> tm = m.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getValue, ...));

At the moment I can't use see how to get from https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html to where I need to be.

John Estess
  • 576
  • 10
  • 22

3 Answers3

7

I think Collectors.groupingBy makes more sense than Collectors.toMap to achieve what you are looking for:

Map<Long, List<Integer>> tm = 
    m.entrySet()
     .stream()
     .collect(Collectors.groupingBy(Map.Entry::getValue, // group the entries by the 
                                                         // value (the frequency)
                                    TreeMap::new, // generate a TreeMap
                                    Collectors.mapping (Map.Entry::getKey, 
                                                        Collectors.toList()))); // the
                                                         // value of the output TreeMap 
                                                         // should be a List of the 
                                                         // original keys

You can replace Collectors.toList() with Collectors.toCollection(ArrayList::new) to make sure that the values of the output Map are ArrayLists (though the current implementation of toList() already results in java.util.ArrayList instances).

For your sample input, this produces the following TreeMap:

{1=[1, 3, 5, 6, 8], 2=[2], 3=[4]}
Eran
  • 387,369
  • 54
  • 702
  • 768
7

I wouldn't use streams to create the inverted map. Instead, I would just do:

Map<Long, List<Integer>> tm = new TreeMap<>();
m.forEach((num, freq) -> tm.computeIfAbsent(freq, k -> new ArrayList<>()).add(num));

System.out.println(tm); // {1=[1, 3, 5, 6, 8], 2=[2], 3=[4]}

As the code to create the inverted map is short, you could use Collectors.collectingAndThen to create both the frequencies and inverted map in one step:

TreeMap<Long, List<Integer>> invertedFrequenciesMap = array.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.groupingBy(Function.identity(), Collectors.counting()),
        map -> {
            TreeMap<Long, List<Integer>> tm = new TreeMap<>();
            map.forEach((num, freq) ->
                    tm.computeIfAbsent(freq, k -> new ArrayList<>()).add(num));
            return tm;
        }));
fps
  • 33,623
  • 8
  • 55
  • 110
6

You are almost correct in your reasoning... Just that the 3-rd argument is where you merge your values for the same key - so you can't omit it.

 TreeMap<Long, List<Integer>> tm = m.entrySet().stream()
            .collect(Collectors.toMap(
                  Entry::getValue, 
                  x -> {
                      List<Integer> list = new ArrayList<>();
                      list.add(x.getKey());
                      return list;
                  }, 
                  (left, right) -> {
                     left.addAll(right);
                     return left;
                  }, 
                  TreeMap::new));
Eugene
  • 117,005
  • 15
  • 201
  • 306