I have a
TreeMap resMap new TreeMap<String, Map<String, String>>();
I would like to filter and keep only entries that values contains a known pair, let's say ('mike' => 'jordan'), and avoid a loop like below
Is there in my included libraries apache.commons and google.common a filter method (that probably would do a loop too, but at least it's less verbose
for (Entry<String, TreeMap<String, String>> el : resMap.entrySet()){
if (el.getValue().get("mike").equals("jordan")){
//
}
}
You can use filters from Guava and the Predicate interface.
Predicate<T> yourFilter = new Predicate<T>() {
public boolean apply(T o) {
// your filter
}
};
So, simple example would be:
Predicate<Integer> evenFilter = new Predicate<Integer>() {
public boolean apply(Integer i) {
return (i % 2 == 0);
}
};
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Map<Integer, Integer> evenMap = Maps.filterValues(map, evenFilter);
Rather than force your client code to use a filter/loop, build what you need into the API of your class:
public class MyClass {
private TreeMap resMap new TreeMap<String, Map<String, String>>();
public void filter(String key, String value) {
// Some impl here. Either your loop or the guava approach
}
}
BTW, if you use your loop, consider changing to this:
for (Iterator<Map.Entry<String, TreeMap<String, String>>> i = resMap.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, TreeMap<String, String>> entry = i.next();
if (value.equals(entry.getValue().get(key))) {
i.remove();
}
}
The changes to the loop are:
Changed order of equals to avoid NPE
Using iterator to allow removal of entries directly
Even if you don't have a class, you could easily wrap it up in a static method on a utility class, where it could also easily be parameterized to work with any nested map:
public static <K1, K2, V> void filter(Map<K1, Map<K2, V>> map, K2 key, V value) {
// Some impl here
}
Here's a non-guava impl for the static method:
for (Iterator<Map.Entry<K1, Map<K2, V>>> i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry<K1, Map<K2, V>> entry = i.next();
if (value.equals(entry.getValue().get(key))) {
i.remove();
}
}
From #Ferrybig answer in this post.
You can use the Java 8 method Collection.removeIf for this purpose:
map.values().removeIf(Object o -> o.get("mike").equals("jordan"));
This removed all values that match the predicate.
Online demo
This works by the fact that calling .values() for a HashMap returns a collection that delegated modifications back to the HashMap itself, meaning that our call for removeIf() actually changes the HashMap (this doesn't work on all java Map's)
Take a look at Guava's Predicates and Functions.
Here are two examples. The both print the key based on match in the value's properties.
private static void printMatchingEntriesUsingALoop(Map<String, Map<String, String>> resMap, String key, String value) {
for (Map.Entry<String, Map<String, String>> entry : resMap.entrySet())
if (value.equals(entry.getValue().get(key)))
System.out.println(entry.getKey());
}
private static void printMatchingEntriesUsingGuava(Map<String, Map<String, String>> resMap, final String key, final String value) {
Predicate<Map<String, String>> keyValueMatch =
new Predicate<Map<String, String>>() {
#Override
public boolean apply(#Nullable Map<String, String> stringStringMap) {
return value.equals(stringStringMap.get(key));
}
};
Maps.EntryTransformer<String, Map<String, String>, Void> printKeys =
new Maps.EntryTransformer<String, Map<String, String>, Void>() {
#Override
public Void transformEntry(#Nullable String s,
#Nullable Map<String, String> stringStringMap) {
System.out.println(s);
return null;
}
};
Maps.transformEntries(Maps.filterValues(resMap, keyValueMatch), printKeys);
}
public static void main(String... args) {
Map<String, Map<String, String>> resMap = new TreeMap<String, Map<String, String>>();
printMatchingEntriesUsingALoop(resMap, "first", "mike");
printMatchingEntriesUsingGuava(resMap, "first", "mike");
}
One uses a loop and one use Guava.
While the first one performs better, you should really decide which will be the easiest to understand and maintain.
Some suggestions from #missingfaktor. You have to use your own judgement, but he highlighted some of the issues well.
a lot of code duplication.
special case handling.
More cyclomatic complexity.
More chances of error, as a result of first three bullets.
Hard to follow code.
Imagine you are a new developer who has to support this software. Which would you rather be facing?
You can filter the map using java 8 and streams. The first step in this process is converting to a stream using entrySet().stream(). This gives you a Stream<Map.Entry<String, TreeMap<String, String>>. You can then use filter(...) to filter the list. When you filter, you should return true when the incoming value should be included in the filter result. After you filtered the results, you can use foreach to loop over the final result.
The final result will look like the following:
resMap.entrySet().stream()
.filter(e -> el.getValue().get("mike").equals("jordan"))
.foreach(e -> {
// Do something with your entry here
});
Related
The method takes in two parameters - a Map and a Set. Converts the Set to a List and starts looking for a match-a List item with a key in the Map.If a match occurs, it copies an element of the old Map to the new Map.
public Map<String, Boolean> getValidMap(Set<String> set, Map<String, Boolean> map) {
Map<String, Boolean> validMap = new HashMap<>();
List<String> mainList = new ArrayList<>(set);
for (String listRule : mainList) {
for (Map.Entry<String, Boolean> mapRule : map.entrySet()) {
if (listRule.equals(mapRule.getKey()))
validMap.put(mapRule.getKey(), mapRule.getValue());
}
}
return validMap;
}
I would like to replace the loops FOR and the IF condition with lambda expressions and streams.I am not familiar with streams and lambdas so I ask for help with this question.
Basically, you can stream the Map and then filter entries having the key in input set and finally collect those entries into Map and return it
return map.entrySet().stream()
.filter(entry->set.contains(entry.getKey())
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
You can just use a for directly from the Set and use computeIfPresent:
public static Map<String, Boolean> getValidMap2(Set<String> set, Map<String,
Boolean> map) {
Map<String, Boolean> validMap = new HashMap<>();
set.forEach(s -> map.computeIfPresent(s, validMap::put));
return validMap;
}
I would like to have a method that maps a List to a NavigableMap. The method call expects an parameter that is used as map key. This parameter is an attribute of the list objects.
Something like this, so both calls are ok:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject:.getString());
NavigableMap<Date, MyObject> dateKeyMap = asNavMap(list, MyObject::getDate());
I dont know how to define the second parameter (MyObject::getDate()). Do I have to use a lambda expression (p -> p.getDate()) or something like Predicate or Function?
I've tried to derive a solution from Approach 8 (or simular) from http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html, but I don't know how to do.
This is what I have done so far:
The concrete implementation:
public class ConcreteConverter {
public static NavigableMap<Integer, Pair<Integer, String>> asNavMap(List<Pair<Integer, String>> pairs) {
NavigableMap<Integer, Pair<Integer, String>> navMap = new TreeMap<>();
for (Pair<Integer, String> pair : pairs) {
navMap.put(pair.getKey(), pair);
}
return navMap;
}
public static void main(String[] args) {
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> map = ConcreteConverter.asNavMap(pairs);
}
}
class Pair<K, V> {
K key;
V val;
// constructor, getter, setter
}
Here I stuck (??? is an attribute of the Pair object):
public static <K, V> NavigableMap<K, V> asNavMap(List<V> items, ???) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items) {
navMap.put(???, item);
}
return navMap;
}
Please notice I have barely experiences writing generic methods or using lambda functions/interfaces.
Any help is appreciated.
Edit 1
As Nick Vanderhofen mentioned I didn't clarify the search for a generic solution.
You can do that with a Function. You keep the code you wanted:
List<MyObject> list = new ArrayList<>();
NavigableMap<String, MyObject> stringKeyMap = asNavMap(list, MyObject::getKey);
The method asNavMap can then take a Function:
private NavigableMap<String,MyObject> asNavMap(List<MyObject> list, Function<MyObject, String> getKey) {
//the actual mapping goes here
}
The getKey method you are specifying can either be a simple getter on the MyObject:
public String getKey(){
return key;
}
Or you could create a static method to get the same result:
public static String getKey(MyObject myObject){
return myObject.getKey();
}
To apply the function you can just use the apply method:
String key = getKey.apply(someObject);
For the actual mapping implementation you can keep your for loop, or you could rewrite it using java 8 and re-use the Function that you got as a parameter in the collector. However, since you want a TreeMap, the syntax is quite verbose:
items.stream().collect(Collectors.toMap(mapper, Function.identity(), (a,b) -> a, TreeMap::new));
Just figured out a working solution!
Still reading http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#approach7 I've tried to use Function, and now this is my solution:
public static <K, V> NavigableMap<K, V> asNavigableMap(List<V> items, Function<V, K> mapper) {
NavigableMap<K, V> navMap = new TreeMap<>();
for (V item : items)
navMap.put(mapper.apply(item), item);
return navMap;
}
And these calls work:
List<Pair<Integer, String>> pairs = new ArrayList<>();
pairs.add(new Pair<Integer, String>(1, "one"));
NavigableMap<Integer, Pair<Integer, String>> navI2P1 = GenericConverter.asNavigableMap(pairs, Pair::getKey);
NavigableMap<String, Pair<Integer, String>> navI2P2 = GenericConverter.asNavigableMap(pairs, Pair::getVal);
It was hard for me to understand the Function functional interface and the apply method.
Thanks to anyone!
Is there a convenient way for such conversion other than a for loop such as
List<Map.Entry<String, Integer>> entryList = new List<>(//initialization);
List<String>> keyList = new List<>(entryList.size());
for (Map.Entry<String, Integer> e : entryList) {
keyList.add(e.getKey());
}
I would like the order to be preserved.
Use java 8 streams to convert this:
List<Map.Entry<String, ?>> entryList = new List<>(//initialization);
List<String> stringList = entryList.stream().map(Entry::getKey).collect(Collectors.toList());
This makes a stream of the entries, then uses the map method to convert them to strings, then collects it to a list using Collectors.toList().
Alternatively, this method can be changed in a helper function if you need it more times like this:
public static <K> List<K> getKeys(List<Map.Entry<K,?>> entryList) {
return entryList.stream().map(Entry::getKey).collect(Collectors.toList());
}
public static <V> List<V> getValues(List<Map.Entry<?,V>> entryList) {
return entryList.stream().map(Entry::getValue).collect(Collectors.toList());
}
While the above code works, you can also get a List<K> from a map by doing new ArrayList<>(map.keySet()), with this having the advantage than you don't need to convert the entryset to a list, before converted to a stream, and then back a list again.
If you do not really need to make a copy of the list you can implement a wrapper arround the list like this, with the adicional bonus that changes made to the entryList are automatically reflected in the stringList. Bear in mind that this simple wrapper is read only.
List<Map.Entry<String, ?>> entryList = new List<>(//initialization);
List<String> stringList = new AbstractList<String>() {
List<Map.Entry<String, Integer>> internal = entryList;
public String get(int index) {
return internal.get(index).getKey();
}
public int size() {
return internal.size();
}
};
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.
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;
}
}
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);
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.
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 :-)
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.
Alternatevely you can make a copy of the original map and iterate over the values removing those whose length is less than x.
Is there a more elegant/built-in way to reverse the keys and values of a Hashmap?
I currently have the following.
private Map<Boolean, List<String>> reverseMap(Map<String, Boolean> permissions) {
List<String> allow = new ArrayList<String>();
List<String> deny = new ArrayList<String>();
Map<Boolean, List<String>> returnvalue = new HashMap<Boolean, List<String>>();
for (Entry<String, Boolean> entry : permissions.entrySet()) {
if(entry.getValue()) {
allow.add(entry.getKey());
} else {
deny.add(entry.getKey());
}
}
returnvalue.put(true, allow);
returnvalue.put(false, deny);
return returnvalue;
}
You might consider using one of Guava's Multimap implementations. For example:
private Multimap<Boolean, String> reverseMap(Map<String, Boolean> permissions) {
Multimap<Boolean, String> multimap = ArrayListMultimap.create();
for (Map.Entry<String, Boolean> entry : permissions.entrySet()) {
multimap.put(entry.getValue(), entry.getKey());
}
return multimap;
}
Or more generally:
private static <K, V> Multimap<V, K> reverseMap(Map<K, V> source) {
Multimap<V, K> multimap = ArrayListMultimap.create();
for (Map.Entry<K, V> entry : source.entrySet()) {
multimap.put(entry.getValue(), entry.getKey());
}
return multimap;
}
I'd do something similar (but if you must do this kind of thing frequently, consider Guava), only replacing the List with Set (seems a little more consistent) and prefilling the reversemap:
private Map<Boolean, Set<String>> reverseMap(Map<String, Boolean> permissions) {
Map<Boolean, Set<String>> returnvalue = new HashMap<Boolean, Set<String>>();
returnvalue.put(Boolean.TRUE, new HashSet<String>());
returnvalue.put(Boolean.FALSE, new HashSet<String>());
for (Entry<String, Boolean> entry : permissions.entrySet())
returnvalue.get(entry.getValue()).add(entry.getKey());
return returnvalue;
}
First thing to note is that you don't really need a reverse map if your values are only true or false. It will make sense if you have a broader range of values.
One easy (but not very elegant) way to get the entries with a specific value is:
public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
Set<T> keys = new HashSet<T>();
for (Entry<T, E> entry : map.entrySet()) {
if (entry.getValue().equals(value)) {
keys.add(entry.getKey());
}
}
return keys;
}
You can see that this is not so good if you need to call it every now and then. It makes sense to have two different maps (straight and reverse) and add entries to both. You can't use Bidi maps since there is no 1:1 relation between keys and values.
UPDATE: The following solution won't work. See comments.
You can also consider using a TreeMap and keep it sorted based on the value. This way you can have a sorted set by calling map.entrySet() any time (denies entries first, then allows). The drawback is that it is only one set.
ValueComparator bvc = new ValueComparator(map);
TreeMap<String,Boolean> sorted_map = new TreeMap(bvc);
class ValueComparator implements Comparator {
Map base;
public ValueComparator(Map base) {
this.base = base;
}
public int compare(Object a, Object b) {
return (Boolean)base.get(a).compareTo((Boolean)base.get(b));
}
}
Guava's BiMap already provides a method for reversing its key-value pairs. Perhaps you could change the interface of the Map in question to BiMap, or else use the following code:
private BiMap<Boolean, String> reverseMap(Map<String, Boolean> permissions) {
BiMap<String, Boolean> bimap = HashBiMap.create(permissions);
return bimap.inverse();
}