I have a list of maps List<Map<String, Object>>
.
Now I need to check whether a map exists in this list or not.
The list looks like:
List<Map<String, Object>> contentList = new ArrayList<>();
Map<String,Object> objectMap = new HashMap<>();
objectMap.put("id", "1");
objectMap.put("name", "name1");
objectMap.put("desc", "desc1");
objectMap.put("str", "str1");
contentList.add(objectMap);
Map<String,Object> objectMap2 = new HashMap<>();
objectMap2.put("id", "2");
objectMap2.put("name", "name2");
objectMap2.put("desc", "desc2");
objectMap2.put("str", "str2");
contentList.add(objectMap2);
now I have a map like:
Map<String,Object> expectedMap = new HashMap<>();
expectedMap.put("id", "2");
expectedMap.put("name", "name2");
expectedMap.put("desc", "desc2");
I need to check whether expectedMap
exists in the list or not.
I use this method to check it now. But I don't know if there is a more efficient way to do the same thing?
AtomicBoolean found = new AtomicBoolean(false);
contentList.forEach( m ->{
if (m.entrySet().containsAll(expectedMap.entrySet())){
found.set(true);
}
});
return found.get();
And is there any way to make this method case-insensitive for expectedMap
keys?
For example, I expect it also works for map:
Map<String,Object> expectedMap = new HashMap<>();
expectedMap.put("Id", "2");
expectedMap.put("Name", "name2");
expectedMap.put("Desc", "desc2");
CodePudding user response:
In order to compare keys in case-insensitive manner, a simple solution would be to generate a string of each map and then simply compare strings. Of course, this would be inefficient for large maps.
For example:
// A simple method to concatenate entries of map as key=value pairs (in sorted key order)
private static String toKVString(Map<String,?> map, String delimiter) {
return map.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.map(e -> String.format("%s=%s", e.getKey().toLowerCase(), e.getValue()))
.collect(Collectors.joining(delimiter));
}
Then, we can use simple string equality:
final String delim = "/"; // choose something not found in keys or values
final String expectedMapAsString = toKVString(expectedMap, delim);
boolean found = contentList.stream()
.anyMatch(map -> expectedMapAsString.equals(toKVString(map, delim)));
CodePudding user response:
But I don't know if there is a more efficient way to do the same thing?
In order to find out whether a single map in a list matches the given criteria you don't need to check every element, instead you need to find the first match and break the iteration.
To achieve that in place of forEach
and AtomicBoolean
, which act much slower than a simple boolean
flag, you can use a plain for
loop with a break
statement inside it.
Or you can make use of the Java 8 streams with anyMatch()
as terminal operation. anyMatch()
- is a so-called short circuit operation, it'll break when the first element matching the given predicate will be found.
return contentList.stream()
.anyMatch(map -> map.navigableKeySet().containsAll(expectedMap.navigableKeySet()));
How to make this method case-insensitive for "expectedMap" keys?
It's not possible to achieve using hash-based collections.
But you can utilize in place of the Map
the NavigableMap
interface, and it's implementation TreeMap
by providing a case-insensitive Comparator
. And there's a predefined case-insensitive comparator strings provided by the String
class as its static field.
List<NavigableMap<String, Object>> contentList = new ArrayList<>();
NavigableMap<String,Object> objectMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
...
Note, that maintaining an ordered collection has a cost. Contrary to HashMap
which allows to add and retrieve entries in constant time O(1), methods like get()
, put()
, containsKey()
will perform in O(log n) with TreeMap
.
You can play around with this Online Demo