0

I'm having a hard time converting a Map containing some integers as keys and a list of random strings as values.

E.g. :

1 = ["a", "b", "c"]
2 = ["a", "b", "z"]
3 = ["z"]

I want to transform it into a Map of distinct strings as keys and lists the integers as values.

E.g. :

a = [1, 2]
b = [1, 2]
c = [1]
z = [2,3]

Here's what I have so far:

Map<Integer, List<String>> integerListMap; // initial list is already populated

List<String> distinctStrings = new ArrayList<>();
SortedMap<String, List<Integer>> stringListSortedMap = new TreeMap<>();

for(Integer i: integers) {
    integerListMap.put(i, strings);
    distinctStrings.addAll(strings);
}

distinctStrings = distinctStrings.stream().distinct().collect(Collectors.toList());

for(String s : distinctStrings) {
    distinctStrings.put(s, ???);
}
2
  • 1
    Iterate over your source Map and populate the result directly in that loop. There's no need to create a distinctStrings List. Commented Sep 30, 2022 at 13:03
  • Your initial data and result is written in such a way that one can't tell if you want the result in sorted key order, encounter order, or you don't care. Map keys are unordered by design. Commented Oct 1, 2022 at 13:08

2 Answers 2

2

Iterate over your source map's value and put each value into the target map.

final Map<String, List<Integer>> target = new HashMap<>();
for (final Map.Entry<Integer, List<String>> entry = source.entrySet()) {
  for (final String s : entry.getValue()) {
    target.computeIfAbsent(s, k -> new ArrayList<>()).add(entry.getKey());
  }
}

Or with the Stream API by abusing Map.Entry to build the inverse:

final Map<String, List<Integer>> target = source.entrySet()
  .stream()
  .flatMap(e -> e.getValue().stream().map(s -> Map.entry(s, e.getKey()))
  .collect(Collectors.groupingBy(e::getKey, Collectors.mapping(e::getValue, Collectors.toList())));

Although this might not be as clear as introducing a new custom type to hold the inverted mapping.

Another alternative would be using a bidirectial map. Many libraries come implementations of these, such as Guava.

Sign up to request clarification or add additional context in comments.

5 Comments

Arghs! That's what you get for not using var. Stupid, careless typos. Thanks for editing!
that's what happens when you don't have an IDE available I guess :) you're welcome.
Looks like the OP may want the keys in sorted (or at least encounter order). You may want to change to a TreeMap or LinkedHashMap.
@WJS where does it state that? But yes, LinkedHashMap will keep keys in insertion order, TreeMap in natural order or as defined by the specified Comparator instance
@knittl It doesn't state that. I posed that question to the OP. But based on the initial data and resultant map, it is not possible to tell since they are such simple examples.
2

There's no need to apply distinct() since you're storing the data into the Map and keys are guaranteed to be unique.

You can flatten the entries of the source map, so that only one string (let's call it name) and a single integer (let's call it number) would correspond to a stream element, and then group the data by string.

To implement this problem using streams, we can utilize flatMap() operation to perform one-to-many transformation. And it's a good practice to define a custom type for that purpose as a Java 16 record, or a class (you can also use a Map.Entry, but note that approach of using a custom type is more advantages because it allows writing self-documenting code).

In order to collect the data into a TreeMap you can make use of the three-args version of groupingBy() which allows to specify mapFactory.

record NameNumber(String name, Integer number) {}
        
Map<Integer, List<String>> dataByProvider = Map.of(
    1, List.of("a", "b", "c"),
    2, List.of("a", "b", "z"),
    3, List.of("z")
);
        
NavigableMap<String, List<Integer>> numbersByName = dataByProvider.entrySet().stream()
    .flatMap(entry -> entry.getValue().stream()
        .map(name -> new NameNumber(name, entry.getKey()))
    )
    .collect(Collectors.groupingBy(
        NameNumber::name,
        TreeMap::new,
        Collectors.mapping(NameNumber::number, Collectors.toList())
    ));
    
numbersByName.forEach((name, numbers) -> System.out.println(name + " -> " + numbers));

Output:

a -> [2, 1]
b -> [2, 1]
c -> [1]
z -> [3, 2]

Sidenote: while using TreeMap it's more beneficial to use NavigableMap as an abstract type because it allows to access methods like higherKey(), lowerKey(), firstEntry(), lastEntry(), etc. which are declared in the SortedMap interface.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.