Easy solution with streams
Step 1 : get the maxValue
final Integer maxValue = map.values()
.stream()
.max(Comparator.naturalOrder())
.orElse(null);
or more simply:
// But be aware of NoSuchElementException if the the map is empty.
final Integer maxValue = Collections.max(map.values());
Step 2 : get keys associated with this max value:
List<String> result = map.entrySet()
.stream()
.filter(e -> e.getValue().equals(maxValue))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Only one pass
OP asked if we can do it one pass. I really don't think it's necessary as you can't do better than O(n) and it's likely that a one pass solution could be slower (as you need to keep a list of the keys that you might not need).
With a for loop
Quite easy and clear
ArrayList<String> result = new ArrayList<>();
int max = Integer.MIN_VALUE;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
Integer value = entry.getValue();
if(value < max) continue;
if(value > max){
max = value;
result = new ArrayList<>();
}
// this applies in both > max and == max cases
result.add(entry.getKey());
}
With streams
If we absolutely want to use streams
, we can actually convert the code above to streams and we can use collect
. But I really wouldn't suggest this approach as it makes everything more complicated. Also since the accumulator needs to keep both the keys and the max value, you need to create a private class to hold the data.
So this is just for the sake of the exercise/brain teaser.
List<String> result = map.entrySet()
.stream()
.collect(MaxData::supply, MaxData::accumulate, MaxData::combine)
.keys;
And the accumulator class:
public static class MaxData {
int max;
List<String> keys;
private MaxData(int max, List<String> keys) {
this.max = max;
this.keys = keys;
}
public static MaxData supply() {
return new MaxData(Integer.MIN_VALUE, null);
}
public void accumulate(Map.Entry<String, Integer> entry) {
Integer entryValue = entry.getValue();
if (entryValue > max) {
max = entryValue;
keys = new ArrayList<>();
keys.add(entry.getKey());
} else if (entryValue == max) {
keys.add(entry.getKey());
}
// last case is entryValue < max, we keep the current max
}
public void combine(MaxData other) {
if (other.max > max){
max = other.max;
keys = other.keys;
} else if(other.max == max) {
keys.addAll(other.keys);
}
// last case is entryValue < max, we keep the current max
}
}