rocky
rocky

Reputation: 76

Java8 Create Map grouping by key contained in values

I have the following two lists of String:

{APPLE, ORANGE, BANANA} //call it keyList
{APPLE123, ORANGEXXX, 1APPLE, APPLEEEE} //call it valueList

Desired output is an HashMap<String, List<String>> like this:

<APPLE, {APPLE123, 1APPLE, APPLEEEE}>
<ORANGE, {ORANGEXXX}>
<BANANA, {}> //also <key, null> is accepted

I have implemented this solution(it works)

HashMap<String, List<String>> myMap = new HashMap<>();
keyList.forEach(key -> {
    List<String> values = valueList.stream()
            .filter(value -> value.contains(key))
            .collect(Collectors.toList());
    myMap.put(key, values);
});

Given the assumption that a value is related to only one key (it's a constraint of my domain), is this the best solution in java8 , in terms of performance and/or code cleaning ? Can it be tuned in some way?

Upvotes: 0

Views: 1001

Answers (1)

Holger
Holger

Reputation: 298233

If you can safely assume that each value is associated with a key, and only one key, you can go into the following direction:

Pattern p = Pattern.compile(String.join("|", keyList));
Map<String, List<String>> map = valueList.stream()
    .collect(Collectors.groupingBy(s -> {
        Matcher m = p.matcher(s);
        if(!m.find()) throw new AssertionError();
        return m.group();
    }));

map.forEach((k,v) -> System.out.println(k+": "+v));

If the keys may contain special characters which could get misinterpreted as regex constructs, you can change the preparation code to

Pattern p = Pattern.compile(
    keyList.stream().map(Pattern::quote).collect(Collectors.joining("|")));

The collect operation does only create the groups for existing values. If you really need all keys to be present, you can use

Map<String, List<String>> map = valueList.stream()
    .collect(Collectors.groupingBy(s -> {
            Matcher m = p.matcher(s);
            if(!m.find()) throw new AssertionError();
            return m.group();
        },
        HashMap::new, // ensure mutable map
        Collectors.toList()
    ));
keyList.forEach(key -> map.putIfAbsent(key, Collections.emptyList()));

or

Pattern p = Pattern.compile(
    keyList.stream().map(Pattern::quote)
           .collect(Collectors.joining("|", ".*(", ").*")));
Map<String, List<String>> map = valueList.stream()
    .map(p::matcher)
    .filter(Matcher::matches)
    .collect(Collectors.groupingBy(m -> m.group(1),
        HashMap::new, // ensure mutable map
        Collectors.mapping(Matcher::group, Collectors.toList())
    ));
keyList.forEach(key -> map.putIfAbsent(key, Collections.emptyList()));

Upvotes: 1

Related Questions