2

I wish to convert the following

enum Parameter {Foo, Bar, Baz};

Map<String, EnumMap<Parameter, String>> myMap = new HashMap(){{
   put("KEY1", new EnumMap<>(Parameter.class) {{
      put(Parameter.Foo, "BAD");
      put(Parameter.Bar, "GOOD");
      put(Parameter.Baz, "BAD");
   }});
   put("KEY2", new EnumMap<>(Parameter.class) {{
      put(Parameter.Foo, "BAD");
      put(Parameter.Bar, "GOOD");
      put(Parameter.Baz, "GOOD");
   }});
}};

into a filtered map where the filter will only contain either the

Map<String, List<Entry<Parameter, String>>> mapList

Where the resultant list should contain the rows with "GOOD" as the filter criteria.

I cannot figure out the correct stream syntax to do this, I know its along the following lines:

Map<String, List<Entry<Parameter, String>>> mapList =
    myMap.entrySet().stream().groupingBy(e -> e.getKey()).collect(Collectors.toMap(????)
5
  • 3
    I have to ask, why do you have such contrived code to begin with? A map of a EnumMap and you want to make a Map of List of Map entries? Wouldn't it be better to have proper objects? Commented Jun 28, 2016 at 20:40
  • The actual data comes from a CSV file which is organized at the top most level by service, then within each service the EnumMap parameters are actually mappings from fixed device types to their corresponding IP address strings, so for ex. The CSV is fixed and not split in to separate tables. In this way the (e -> e.getKey()) above will still be that same high level service name key (the service names are KEY1 & KEY2 in my contrived example) Commented Jun 28, 2016 at 20:47
  • 1
    Sure but it would be far simpler to have one line of your CSV be represented by an actual type. Create a class Service with a name and a list of things (needs a better name) that would contain the device type, the address, etc. Commented Jun 28, 2016 at 20:50
  • 1
    I'm not clear on what the end result should look like. Commented Jun 28, 2016 at 20:56
  • 1
    The end result should be {"KEY1" , {{Parameter.Bar, "GOOD"}}}, {"KEY2", {{Parameter.Bar, "GOOD"}, {Parameter.Baz, "GOOD"}}} Commented Jun 28, 2016 at 21:01

2 Answers 2

3

It’s not clear why you emphasize the EnumMap nature of the value, as the logic doesn’t change when using that specific Map type. You have a map whose values you want to convert, retaining the keys, which implies that streaming over the entries and collecting via Collectors.toMap is the way to go, letting the key function return the original key and the value function do the actual conversion.

The values happen to be a Map which you want to convert to a List<Map.Entry>, which is quite easy, streaming over the entries and collecting them to a List. Adding a filter to accept "GOOD" values only, is trivial.

Now, it’s a pity that you buried another important requirement in a comment instead of specifying it in the question. You want to leave out keys of the outer map if there’s no "GOOD" value in its target map at all. So you need a filter for the outer stream operation, which isn’t that hard, if you remember how powerful the Collection API is, even without Streams:

Map<String, List<Map.Entry<Parameter, String>>> mapList
    = myMap.entrySet().stream()
        .filter(e -> e.getValue().containsValue("GOOD")) // need at least one GOOD there
        .collect(Collectors.toMap(
            Map.Entry::getKey, // keep original keys
            e -> e.getValue().entrySet().stream() // actual conversion
                .filter(sub -> sub.getValue().equals("GOOD")) // GOOD values only
                .collect(Collectors.toList()) // make List<Map.Entry>
            ));
Sign up to request clarification or add additional context in comments.

Comments

2

The entries are already grouped correctly, just the values need to be unwrapped and filtered:

import static java.util.stream.Collectors.*;
...
Map<String, List<Entry<Parameter, String>>> mapList = new HashMap<>();

myMap.forEach((k, v) -> { 
   List<Entry<Parameter, String>> filtered = v.entrySet().stream()
   .filter(e -> e.getValue().equals("GOOD"))
   .collect(toList());

   if(!filtered.isEmpty()) mapList.put(k, filtered);
});

5 Comments

Very nice, just one little issue, in my actual data, I have some entire rows in myMap that have no matching entries - the above will have an entry in mapList with empty values, how would I filter those entries out - forgive my newbie questions as I am new to the streams api
I figured out how to only insert non empty values by checking the v.entrySet().stream().filter(...) , In my case this turns out to be forEach((k, v) -> {List<Entry<Parameter, String>> ports = v.entrySet().stream().filter(e -> !e.getValue().equals("0:00")).collect(Collectors.toList()); if (!ports.isEmpty())portInfo.put(k, validPorts);}); - yeuk but it works
@johnco3 I was about to suggest the same. I was looking for a nicer way, but I think that is the best one.
Thanks Jorn, either way its pretty horrible, BTW I was wondering if you think it might be possible to do this without having the separate mapList variable and instead doing it in a sort of stream all the way oneliner. Just curious.
@johnco3 I looked at it, but there would be the same problem with empty lists, which could only be solved by doing 2 passes over the entries, instead of just the one you have now.

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.