How replace the loops FOR and the IF condition with lambda expressions? - java

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;
}

Related

Checking if value present in a nested Map using lambda expression

I have a nested map as Map<String, Map<String, Boolean>> and would like to find if the inner map has at least one value as TRUE. I was able to do it using a loop but trying to do it using lambda expression
Using for loop:
Map<String, Map<String, Boolean>> maps = new HashMap<>();
Map<String, Boolean> test = new HashMap<>();
test.put("test1", Boolean.FALSE);
test.put("test2", Boolean.TRUE);
maps.put("hey", test);
Map<String, Boolean> testtt = new HashMap<>();
testtt.put("test3", Boolean.FALSE);
testtt.put("test4", Boolean.TRUE);
maps.put("lol", testtt);
Boolean val = Boolean.FALSE;
for(Map.Entry<String, Map<String, Boolean>> m: maps.entrySet()){
Map<String, Boolean> mm = m.getValue();
for(Map.Entry<String, Boolean> mmm: mm.entrySet()){
if(mmm.getValue()){
val = Boolean.TRUE;
break;
}
}
}
System.out.println(val);
I was able to do it using a loop but trying to do it using lambda expression.
Here is one way. Here the lambda will be based on a Predicate
first you need to stream the outer maps values (which is another map) and then stream the values of those.
Then it's a matter of using anyMatch() with an identity lambda to find the first true value.
Predicate<Map<String, Map<String, Boolean>>> hasTrue = m -> m.values()
.stream().flatMap(map -> map.values().stream()).anyMatch(a->a);
System.out.println(hasTrue.test(maps));
Prints
true
And a better way as suggested by Holger
maps.values().stream()
.anyMatch(innerMap -> innerMap.containsValue(true))
You can try changing the loop on something like this:
Map<String, Map<String, Boolean>> maps = new HashMap<>();
boolean hasValue = maps
.values()
.stream()
.flatMap(m -> m.values().stream())
.anyMatch(b -> b.getValue());

Efficient way to iterate and copy the values of HashMap

I want to convert:
Map<String, Map<String, List<Map<String, String>>>> inputMap
to:
Map<String, Map<String, CustomObject>> customMap
inputMap is provided in the config and is ready but I need to customMap Format. CustomObject will be derived from List<Map<String, String>> using few lines of code in a function.
I have tried a normal way of iterating input map and copying key values in customMap. Is there any efficient way of doing that using Java 8 or some other shortcut?
Map<String, Map<String, List<Map<String, String>>>> configuredMap = new HashMap<>();
Map<String, Map<String, CustomObj>> finalMap = new HashMap<>();
for (Map.Entry<String, Map<String, List<Map<String, String>>>> attributeEntry : configuredMap.entrySet()) {
Map<String, CustomObj> innerMap = new HashMap<>();
for (Map.Entry<String, List<Map<String, String>>> valueEntry : attributeEntry.getValue().entrySet()) {
innerMap.put(valueEntry.getKey(), getCustomeObj(valueEntry.getValue()));
}
finalMap.put(attributeEntry.getKey(), innerMap);
}
private CustomObj getCustomeObj(List<Map<String, String>> list) {
return new CustomObj();
}
One solution is to stream the entrySet of inputMap, and then use Collectors#toMap twice (once for the outer Map, and once for the inner Map):
Map<String, Map<String, CustomObj>> customMap = inputMap.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(), entry -> {
return entry.getValue()
.entrySet()
.stream()
.collect(Collectors.toMap(Function.identity(),
entry -> getCustomeObj(entry.getValue())));
}));
You could stream, but that ain't going to look readable; at least to me. So if you have a method:
static CustomObject fun(List<Map<String, String>> in) {
return .... // whatever processing you have here
}
you could still use the java-8 syntax, but in a different form:
Map<String, Map<String, CustomObject>> customMap = new HashMap<>();
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
Map<String, CustomObject> innerMap = new HashMap<>();
innerMap.put(innerKey, fun(listOfMaps));
customMap.put(key, innerMap);
});
});
If you can make the inner map immutable, you could make that even shorter:
inputMap.forEach((key, value) -> {
value.forEach((innerKey, listOfMaps) -> {
customMap.put(key, Collections.singletonMap(innerKey, fun(listOfMaps)));
});
});
IMHO streaming is not so bad idea. There're no bad tools. It depends on how you're using them.
In this particular case I would extract the repeating pattern into an utility method:
public static <K, V1, V2> Map<K, V2> transformValues(Map<K, V1> map, Function<V1, V2> transformer) {
return map.entrySet()
.stream()
.collect(toMap(Entry::getKey, e -> transformer.apply(e.getValue())));
}
The method above can be implemented using any approach, though I think Stream API fits pretty well here.
Once you defined the utility method, it can be used as simple as follows:
Map<String, Map<String, CustomObj>> customMap =
transformValues(inputMap, attr -> transformValues(attr, this::getCustomObj));
The actual transformation is effectively one liner. So with proper JavaDoc for transformValues method the result code is pretty readable and maintainable.
How about Collectors.toMap for the entries both at an outer and inner level such as:
Map<String, Map<String, CustomObj>> finalMap = configuredMap.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
attributeEntry -> attributeEntry.getValue().entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
valueEntry -> getCustomeObj(valueEntry.getValue())))));

Creating a stream to intersect two maps based on their value

I have a method named getNames. Its goal: return the names that occur in both of two maps. I tried rewriting this method to a Stream. But, I don't want testOneNames to be modified in this operation. How to rebuild it as a stream?
private Map<String, List<String>> getNames(Map<String, List<String>> testOneNames, Map<String, List<String>> testSecondNames) {
Map<String, List<String>> copyTestOneName = new HashMap<>(testOneNames);
copyTestOneName.values().retainAll(testSecondNames.values());
return copyTestOneName;
}
You could do the following:
private Map<String, List<String>> getNames(Map<String, List<String>> testOneNames, Map<String, List<String>> testSecondNames) {
return testOneNames.entrySet().stream().filter(e -> testSecondNames.containsValue(e.getValue())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

Java Map, filter with values properties

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
});

Reversing a HashMap from Map<String, Boolean> to Map<Boolean, List<String>>

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();
}

Categories

Resources