Home > database >  How to use Map.computeIfAbsent() in a stream?
How to use Map.computeIfAbsent() in a stream?

Time:05-14

I have a fun puzzler. Say I have a list of String values:

["A", "B", "C"]

Then I have to query another system for a Map<User, Long> of users with an attribute that corresponds to those values in the list with a count:

{name="Annie", key="A"} -> 23
{name="Paul", key="C"} -> 16

I need to return a new List<UserCount> with a count of each key. So I expect:

 {key="A", count=23},
 {key="B", count=0},
 {key="C", count=16}

But I'm having a hard time computing when one of my User objects has no corresponding count in the map.

I know that map.computeIfAbsent() does what I need, but how can I apply it based on what's on the contents of the original list?

I think I need to stream the over the original list, then apply compute? So I have:

valuesList.stream()
 .map(it -> valuesMap.computeIfAbsent(it.getKey(), k-> OL))
 ...

But here's where I get stuck. Can anyone provide any insight as to how I accomplish what I need?

CodePudding user response:

You can create an auxiliary Map<String, Long> which will associate each string key with the count and then generate a list of UserCount based on it.

Example:

public record User(String name, String key) {}
public record UserCount(String key, long count) {}

public static void main(String[] args) {
    List<String> keys = List.of("A", "B", "C");
    
    Map<User, Long> countByUser =
        Map.of(new User("Annie", "A"), 23L,
               new User("Paul", "C"), 16L));
    
    Map<String, Long> countByKey = countByUser.entrySet().stream()
        .collect(Collectors.groupingBy(entry -> entry.getKey().key(), 
            Collectors.summingLong(Map.Entry::getValue)));

    List<UserCount> userCounts = keys.stream()
        .map(key -> new UserCount(key, countByKey.getOrDefault(key, 0L)))
        .collect(Collectors.toList());

    System.out.println(userCounts);
}

Output

[UserCount[key=A, count=23], UserCount[key=B, count=0], UserCount[key=C, count=16]]

Regarding the idea of utilizing computeIfAbsent() with stream - this approach is wrong and discouraged by the documentation of the Stream API.

Sure, you can use computeIfAbsent() to solve this problem, but not in conjunction with streams. It's not a good idea to create a stream that operates via side effects (at least without compelling reason).

And I guess you even don't need Java 8 computeIfAbsent(), plain and simple putIfAbsent() will be sufficient.

The following code will produce the same result:

Map<String, Long> countByKey = new HashMap<>();

countByUser.forEach((k, v) -> countByKey.merge(k.key(), v, Long::sum));
keys.forEach(k -> countByKey.putIfAbsent(k, 0L));

List<UserCount> userCounts = keys.stream()
            .map(key -> new UserCount(key, countByKey.getOrDefault(key, 0L)))
            .collect(Collectors.toList());

And instead of applying forEach() on a map and list, you can create two enhanced for loops if this options looks convoluted.

CodePudding user response:

// First, map keys to users (assuming keys are unique for each user) ...
final Map<String,User> keyToUserMap = valuesMap.keySet().stream()
    .collect(Collectors.toMap(u -> u.key, u -> u));
        
Map<String,Long> map2 = valuesList.stream()
   .map(key -> {
     // Form a pair of (key, count)
     final User user = keyToUserMap.get(key);
     return new Object[] { 
        key, 
        user == null? 0L : valuesMap.get(user).count };
   })
  .collect(Collectors.toMap(pair -> (String) pair[0], pair -> (Long) pair[1]));
  • Related