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]));