permanewb
permanewb

Reputation: 21

Is there a neat way of converting lists into maps with the keys set to the elements of original collection and values set to an enum in java

Java newbie... I have a dictionary like so:

electronictypeModelsMap =
{
"laptops" => ["macbook", "thinkpad", "yoga"],
"desktops" => ["macmini", "imac", "otherDesktop"],
"smartphones" => ["iphone", "galaxy5", "oneplus"]
}

There will only ever be a small number of keys like the 3 above but there will be a few hundred elements in each value (list)....

I want to be able to query it the other way around so : dictionary.get("macbook") would return "laptops"

So I guess I want to convert into a new map with each element of each list as a key with the corresponding value set to the original key. like this:

{
"macbook" => "laptops", 
"thinkpad" => "laptops", 
"yoga"=> "laptops", 
"macmini" => "desktops", 
"imac" => "desktops", 
"otherDesktop" => "desktops",
"iphone" => "smartphones", 
"galaxy5" =>  "smartphones", 
"oneplus" => "smartphones", 
}

Is there a neat way of doing this in Java 11?

In ruby you could do something like this

newMap = Hash.new 
electronictypeModelsMap.each do |k,v|
    v.each do |value| 
        newMap[value] = k 
    end 
end 

Upvotes: 0

Views: 97

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338326

No need to produce another map. You can query the existing map, to find a key whose value contains the target string.

Streams are useful here to make that query.

Make some example data.

Map < String, Set < String > > map =
        Map.of(
                "laptops" , Set.of( "macbook" , "thinkpad" , "yoga" ) ,
                "desktops" , Set.of( "macmini" , "imac" , "otherDesktop" ) ,
                "smartphones" , Set.of( "iphone" , "galaxy5" , "oneplus" )
        );

You said:

dictionary.get("macbook") would return "laptops"

We can get all the entries of a map. Each entry is a key-value pairing, our string leading to a set of strings.

Make a stream of the entries of the map.

Filter the entries, looking for any whose value (the set of strings) contains our target, macbook. Stop looking after finding a match.

The result of this in an Optional entry. We need Optional here because there may be none found, we may have no hits.

In this case of your example, we know we have a hit, so proceed. We know the found entry’s value is a set containing our target. So extract the key, and report.

Optional < Map.Entry < String, Set < String > > > entry =
        map
                .entrySet()
                .stream()
                .filter( ( Map.Entry < String, Set < String > > stringSetEntry ) -> stringSetEntry.getValue().contains( "macbook" ) )
                .findAny();
String result = entry.get().getKey();

See this code run live at IdeOne.com.

result = laptops

Upvotes: 2

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28968

If you're certain that every element in each list of values will be unique (i.e. distinct in the scope of all map) you can take the following approach:

  • Create a stream over the entry set;
  • Flatten the entries using flatMap() by creating a new entry based on each element of the list and a key to which the current list was mapped, so that a value becomes a key and vice versa.
  • Collect the data into a map using Collectors.toMap().
Map<String, List<String>> source = 
    Map.of("laptops", List.of("macbook", "thinkpad", "yoga"),
           "desktops", List.of("macmini", "imac", "otherDesktop"),
           "smartphones", List.of("iphone", "galaxy5", "oneplus"));

Map<String, String> result = 
    source.entrySet().stream()
        .flatMap(entry -> entry.getValue().stream().map(element -> 
                          Map.entry(element, entry.getKey())))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

result.forEach((k, v) -> System.out.println(k + "  =>  " + v));

Output

galaxy5  =>  smartphones
macmini  =>  desktops
macbook  =>  laptops
etc.

Note that if in the initial map will have at least one element (like "macbook") that has been mapped to more than one category, the collector listed above will be unable to resolve duplicates, and it'll cause an IllegalStateException. As a remedy, you need to define how to override the value by providing a mergeFunction as the third argument of toMap(), or utilize the collector Collectors.groupingBy() instead to preserve all the values mapped to the same key.

Upvotes: 2

Eritrean
Eritrean

Reputation: 16498

Assuming you have a map like below:

Map<String,List<String>> electronictypeModelsMap = 
     Map.of("laptops",     List.of("macbook", "thinkpad", "yoga"),
            "desktops",    List.of("macmini", "imac", "otherDesktop"),
            "smartphones", List.of("iphone", "galaxy5", "oneplus"));

the simplest solution could be to use a nested forEach

Map<String,String> reMapped = new HashMap<>();

electronictypeModelsMap.forEach((key,list) -> {
    list.forEach(item -> {
        reMapped.put(item, key);
    });
});

Upvotes: 3

Related Questions