Home > other >  Convert Map<Integer, List<Strings> to Map<String, List<Integer>
Convert Map<Integer, List<Strings> to Map<String, List<Integer>

Time:09-30

i'm having a hard time converting a Map that has Some integers as keys and a list of random strings as values.

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

To a Map of the distinct strings with the integers that have that int as a key

e.g.
a = [1, 2]
b = [1, 2]
c = [1]
z = [2,3]

Here is what i got so far:

Map<Integer, List<String>> integerListMap; <- Initial list 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, )
}

Thanks in advance

CodePudding user response:

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.

CodePudding user response:

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 you to use such methods as higherKey(), lowerKey(), firstEntry(), lastEntry(), etc. which are not accessible with the Map interface.

  • Related