user2781389 user2781389 - 4 months ago 12
Java Question

Adding non-duplicated elements to existing keys in java 8 functional style

I have a map I want to populate:

private Map<String, Set<String>> myMap = new HashMap<>();


with this method:

private void compute(String key, String[] parts) {
myMap.computeIfAbsent(key, k -> getMessage(parts));
}


compute()
is invoked as follows:

for (String line : messages) {
String[] parts = line.split("-");
validator.validate(parts); //validates parts are as expected
String key = parts[parts.length - 1];

compute(key, parts);
}


parts
elements are like this:

[AB, CC, 123]
[AB, FF, 123]
[AB, 456]


In the
compute()
method, as you can see I am trying to use the last part of the element of the array as a
key
and the other parts to be used as
values
for the map I am looking to build.

My Question: How do I add to existing key only the unique values using Java 8 functional style e.g.

{123=[AB, FF, CC]}

Answer

As you requested I added a lambda variant, which just adds the parts via lambda to the map in the compute-method:

private void compute(String key, String[] parts) {
  myMap.computeIfAbsent(key, 
    s -> Stream.of(parts)
               .limit(parts.length - 1)
               .collect(toSet()));
}

But in this case you will only get something like 123=[AB, CC] in your map. Use merge instead, if you want to add also all values which come on subsequent calls:

private void compute(String key, String[] parts) {
  myMap.merge(key, 
    s -> Stream.of(parts)
               .limit(parts.length - 1)
               .collect(toSet()),
               (currentSet, newSet) -> {currentSet.addAll(newSet); return currentSet;});
}

I am not sure what you intend with computeIfAbsent, but from what you listed as parts and what you expect as output, you may also want to try the following instead of the whole code you listed :

// the function to identify your key
Function<String[], String> keyFunction = strings -> strings[strings.length - 1];
// the function to identify your values
Function<String[], List<String>> valuesFunction = strings -> Arrays.asList(strings).subList(0, strings.length - 1);
// a collector to add all entries of a collection to a (sorted) TreeSet 
Collector<List<String>, TreeSet<Object>, TreeSet<Object>> listTreeSetCollector = Collector.of(TreeSet::new, TreeSet::addAll, (left, right) -> {
  left.addAll(right);
  return left;
});

Map myMap = Arrays.stream(messages) // or: messages.stream()
  .map(s -> s.split("-"))
  .peek(validator::validate)
  .collect(Collectors.groupingBy(keyFunction,
      Collectors.mapping(valuesFunction, listTreeSetCollector)));

Using your samples as input you get the result you mentioned (well, actually sorted, as I used a TreeSet).

String[] messages = new String[]{
                "AB-CC-123",
                "AB-FF-123",
                "AB-456"};

produces a map containing:

123=[AB, CC, FF]
456=[AB]
Comments