r123454321
r123454321

Reputation: 3403

Creating subset of HashMap based on some specifications?

So I have the following HashMap:

HashMap<String, List<someDataType>> map;

I want to create a new HashMap that is only composed of the k/v pairs in map that have a value (the list) whose length is less than a certain "x". The only way I know how to do this is to iterate through the HashMap and put k/v pairs into a new HashMap. Is there a more concise way to achieve what I'm looking for? Thanks.

Upvotes: 5

Views: 8384

Answers (6)

dpr
dpr

Reputation: 10972

Nowadays (Java 8+) this could be done with streams:

Predicate<Map.Entry<String, List<String>>> test = entry -> entry.getValue().size() <= x; // note this is java.util.function.Predicate
Map<String, List<String>> filteredMap = map.entrySet().stream().filter(test)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

This helps to avoid the dependency to guava which might be undesired.

Upvotes: 1

Paul Bellora
Paul Bellora

Reputation: 55223

If the Guava library is available to your project, you could use Maps.filterValues (somewhat echoing Keith's answer):

final int x = 42;

Map<String, List<String>> filteredMap =
        Maps.filterValues(map, new Predicate<Collection<?>>() {
            @Override
            public boolean apply(final Collection<?> collection) {
                return collection.size() < x;
            }
        });

Map<String, List<String>> filteredMapCopy = ImmutableMap.copyOf(filteredMap);

Note the need for a copy because filterValues returns a filtered view of the original map.

Update: with Java 8 you can simplify the predicate to a lambda expression:

Map<String, List<String>> filteredMap = Maps.filterValues(map, list -> list.size() < x);

Upvotes: 6

Eric Jablow
Eric Jablow

Reputation: 7899

Going along with the other Guava examples, you can use Guava's MultiMaps:

final MultiMap<K, V> mmap = ArrayListMultiMap.create();
// do stuff.
final int limit = 10;
final MultiMap<K, V> mmapView =
    MultiMaps.filterKeys(mmap, new Predicate<K>(){
        public boolean apply(K k) {
            return mmap.get(k).size() <= limit;
        }
});

The MultiMaps.newListMultiMap method takes arguments you don't want to provide. You can't use MultiMaps.filterValues or .filterEntries here because those use the individual values, not the lists of values. On the other hand, mmap.get(k) never returns null. You cam, of course, use a static inner class that you pass mmap and limit to instead of using anonymous inner classes.

Upvotes: 0

Stewart
Stewart

Reputation: 18303

You may want to look at the Guava library from Google. There's an enormous number of Collections and Map related utils in there, which let you do complex stuff quite concisely. An example of what you can do is:

Iterable<Long> list = 
    Iterables.limit(
        Iterables.filter(
            Ordering.natural()
                    .reverse()
                    .onResultOf(new Function<Long, Integer>() {
                        public Integer apply(Long id) {
                            return // result of this is for sorting purposes
                        }
                    })
                    .sortedCopy(
                        Multisets.intersection(set1, set2)),
                new Predicate<Long>() {
                    public boolean apply(Long id) {
                        return // whether to filter this id
                    }
                }), limit);

I'm sure you can find something in there which can do what you're looking for :-)

Upvotes: 0

Keith
Keith

Reputation: 4184

Using guava:

Map<String, List<String>> newMap = 
    Maps.filterEntries(originalMap, new MyEntryPredicate(10));

where:

private static class MyEntryPredicate implements Predicate<Map.Entry<String, List<String>>> {

    // max list length, exclusive
    private int maxLength;

    private MyEntryPredicate(int maxLength) {
        this.maxLength = maxLength;
    }

    @Override
    public boolean apply(Map.Entry<String, List<String>> input) {
        return input != null && input.getValue().size() < maxLength;
    }
}

Upvotes: 11

Evgeniy Dorofeev
Evgeniy Dorofeev

Reputation: 136062

Alternatevely you can make a copy of the original map and iterate over the values removing those whose length is less than x.

Upvotes: -1

Related Questions