13

Is there a smart way to get all Values from a Map given some Keys?

I would like a method like this:

public static <K, V> Collection<V> getAll(Map<K, V> map, Collection<K> keys)

or is already a guava way?

ColinD
  • 108,630
  • 30
  • 201
  • 202
FlorianOver
  • 997
  • 1
  • 7
  • 11
  • Do you really want to get back a Collection of values rather than a Map? In the process of building that, you'd lose the association between keys and values. – Matt Passell Apr 09 '15 at 15:57

5 Answers5

19

This depends on how you want the method to work. For example, should elements in keys that aren't in map A) just be ignored or should they B) be represented as null in the returned values collection or should that C) be an error? Also consider whether you want a live view or a separate collection containing the values.

For A, my preference would be:

Collection<V> values = Collections2.transform(
    Collections2.filter(keys, Predicates.in(map.keySet()),
    Functions.forMap(map));

This limits the result to values for keys that are actually in the map and should be relatively efficient as well, even if the map is much larger than the set of keys you want. Of course, you may want to copy that result in to another collection depending on what you want to do with it.

For B, you'd use @Michael Brewer-Davis's solution except with Functions.forMap(map, null).

For C, you'd first want to check that map.keySet().containsAll(keys) and throw an error if false, then use @Michael Brewer-Davis's solution... but be aware that unless you then copied the result in to another collection, removing an entry from map could cause an IllegalArgumentException for code using the returned collection at some point.

ColinD
  • 108,630
  • 30
  • 201
  • 202
  • If you do not care about null values, you could also combine A and B: Collection values = Collections2.filter(Collections2.transform(keys, Functions.forMap(map, null)), Predicates.notNull()); This does only one lookup per entry (instead of doing one lookup in Predicates.in() and another one in Functions.forMap()) – Etienne Neveu Apr 20 '11 at 23:34
  • This solution will unlikely produce a performant solution for large maps, and should be avoided except for minimal map sizes where performance does not matter. Also see https://stackoverflow.com/questions/28856781 for a similar question – tkruse Mar 29 '21 at 11:21
14

I agree with skaffman's answer, just not with his conclusion (I think this is better than manual iteration).

Here it is spelled out:

public static <K, V> Collection<V> getAll(Map<K, V> map, Collection<K> keys) {
    return Maps.filterKeys(map, Predicates.in(keys)).values();
}

Also, here's a non-Guava version (Java 8 or higher):

public static <K, V> Collection<V> getAll(Map<K, V> map, Set<K> keys) {
    return map.entrySet()
            .stream()
            .filter(e -> keys.contains(e.getKey()))
            .map(Map.Entry::getValue)
            .collect(Collectors.toList());
}
Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • 3
    The problems I have with using `filterKeys` for this are that A) it requires iterating through every entry of `map`, even if `keys` is much smaller (the other way around seems less likely for a method like this), and B) if the `keys` collection does not have a fast `contains` implementation, calling it once for every entry in the map will increase the order of complexity of the call. The code I gave doesn't have either of those issues. For the non-Guava version, I think a loop would be better... your version requires keeping a whole copy of the map in memory even if the result is empty. – ColinD Apr 20 '11 at 21:30
2

You could, I suppose use Guava's Maps.filteredKeys(), passing in a Predicate which matches your desired keys, but it's not really any better than manual iteration.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • It can actually be much worse, given filteredKeys() returns a live view on the original map, such that e.g. get() and contains() have complexity O(N) instead of O(1). – tkruse Mar 29 '21 at 11:00
2

Using guava: Collections2.transform(keys, Functions.forMap(map));

Michael Brewer-Davis
  • 14,018
  • 5
  • 37
  • 49
  • 2
    Be aware that trying to access an element in the transformed collection for a key that is not in the map will result in an `IllegalArgumentException` though... the overload with a default may be preferable if that's possible. – ColinD Apr 19 '11 at 17:41
1

Java8 Streams:

keys.stream().map( map::get ).collect( Collectors.toList() );

Or if you're concerned about missing keys:

keys.stream().map( k -> map.getOrDefault( k, null ) ).filter(o -> o != null).collect( Collectors.toList() );

Or if you need multiple threads to work (rarely, see @tkruse comment):

keys.parallelStream().map( map::get ).collect( Collectors.toList() );
Code Eyez
  • 313
  • 3
  • 14
  • parallelStream() is very rarely a good idea, due to shared ForkJoin Pool, the answer should have more disclaimers about this, – tkruse Mar 29 '21 at 11:02
  • 1
    Also stream solution with missing keys should filter out nulls before `collect()`. and `collection()` should be `collect()` – tkruse Mar 29 '21 at 11:17