5

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.

r123454321
  • 3,323
  • 11
  • 44
  • 63
  • 1
    You would have to maintain extra constraints on your map to have to not iterate through the whole map. If for instance your map was sorted ascending by the length of the lists, then you could loop until you see a length 1 beyond x. – Hunter McMillen Jun 26 '13 at 21:12
  • @HunterMcMillen: Do you know of a map that sorts based on the values? – jlordo Jun 26 '13 at 21:14
  • @jlordo Not off the top of my head, I imagine it wouldn't be that difficult to implement a Comparator and use a TreeMap with it. – Hunter McMillen Jun 26 '13 at 21:18
  • why don't you like this method? More concise... you can use some library(e.g. google guava) that allows you to do this in a functional way – maks Jun 26 '13 at 21:18
  • @HunterMcMillen: `TreeMap` orders by keys, you can't use it to sort by values, no matter how you write that comparator. – jlordo Jun 26 '13 at 21:24

6 Answers6

11

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;
    }
}
Keith
  • 4,144
  • 1
  • 19
  • 14
  • +1 Beat me to it - but note that `filterEntries` will just return a filtered *view* of the underlying map, in case the OP wants to make a copy of it. Also `filterValues` would be more concise. – Paul Bellora Jun 26 '13 at 21:22
  • Admitting the use of Guava, wouldn't `ArrayListMultiMap` and `MultiMaps.filterkeys` be a better fit? `MultpMaps.filterValues` or `.filterEntries` do not work, because you get each individual value or key-value pair. – Eric Jablow Jun 26 '13 at 21:26
6

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);
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
1

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.

dpr
  • 10,591
  • 3
  • 41
  • 71
0

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 :-)

Stewart
  • 17,616
  • 8
  • 52
  • 80
  • When I began writing this, there were no answers ... when I submitted, there were 2 answers from people who know Guava better than me! :-O – Stewart Jun 26 '13 at 21:27
0

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.

Eric Jablow
  • 7,874
  • 2
  • 22
  • 29
-1

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

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275