Iterate over nested (multi-dimensional) hashmap - java

I have a HashMap<String, Object> that looks like this when I call .toString() on it:
{somekey=false, anotherKey=someString, thirdKey={nestedKey=hello, nestedKey2=world,etc=etcetcetc}}
At a certain point in my script, I would like to iterate over the "thirdKey" set as its own map. Is there a common convention used to isolate a "nested" HashMap and use it as its own one-dimensional map?

Here's my code for recursively extracting all values from the map (and the maps within these map).
public List<Object> getValues(Map<String, Object> map) {
List<Object> retVal = new ArrayList<Object>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
retVal.addAll(getValues((Map) value));
} else {
retVal.add(value);
}
}
return retVal;
}
As Vikdor already said, I don't think there is a real convention for this.
Edit:
You could, of course, also write the keys and values into a new Map ("flattening" it). I just added the values to a List, because this way you don't run into problems when one of the nested maps uses an already present key.

No convention I'm aware of. You have to fall back to instanceof to see if the value at the key is a Map, and treat it specially if it is - in your case recursively.

I doubt if there would be a "common convention" with generics in place. It's best to move towards strongly typed programs and not use Object as either key or value of a hashmap. Then this scenario won't be encountered and one would have more organized data definitions. My two cents!

public static Map<String, Object> flatten(Map<String, Object> map) {
Map<String, Object> newMap = new HashMap<>();
for (Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
#SuppressWarnings("unchecked")
Map<String, Object> tempMap = flatten((HashMap<String, Object>) entry.getValue());
for (Entry<String, Object> tempEntry : tempMap.entrySet()) {
newMap.put(entry.getKey()+"."+tempEntry.getKey(), tempEntry.getValue());
}
} else {
newMap.put(entry.getKey(), entry.getValue());
}
}
return newMap;
}

Related

Java: can't use Map.Entry to iterate over a Map?

Every Java Map iteration example I've seen recommends this paradigm:
for (Map.Entry<String, String> item : hashMap.entrySet()) {
String key = item.getKey();
String value = item.getValue();
}
However, when I attempt to do this I get a warning from my compiler:
Incompatible types: java.lang.Object cannot be converted to java.util.Map.Entry<java.lang.String, java.lang.Object>
Here's my code - the only wrinkle I see is that I'm iterating over an array of Map objects, and then iterating over the elements of the individual Map:
result = getArrayOfMaps();
// Force to List LinkedHashMap
List<LinkedHashMap> result2 = new ArrayList<LinkedHashMap>();
for (Map m : result) {
LinkedHashMap<String, Object> n = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : m.entrySet()) {
n.put(entry.getKey(),entry.getValue());
}
result2.add(n);
}
Am I missing something blatantly obvious?
This is happening because you are using raw types: a List<LinkedHashMap> instead of a List<LinkedHashMap<Something, SomethingElse>>. As a result, the entrySet is just a Set instead of a Set<Map.Entry<Something, SomethingElse>>. Don't do that.

Java - How to iterate over a hashmap of list of hashmap

I want to iterate over a hashmap of list of hashmap.
for example,
HashMap <String, List <HashMap<String, String> > >
How do I iterate over this map.
Please help me to find a solution.
You should probably look over your design, this sounds like something that could be structured in an easier way. Consider maybe breaking this collection up into classes.
But, in case you cannot change it now (or don't have the time), I'll offer a hand.
The easiest way is probably to break the iteration up in several steps:
Iterate over the inner HashMaps
Iterate over the lists
Iterate over the content in the inner HashMaps
Here's the basic code:
for( List<HashMap<String, String>> list : outer.values() ) {
for( HashMap<String, String> map : list ) {
for( String value : map.values() ) {
//Do something
}
}
}
Let us name your map.
HashMap<String, List<HashMap<String, String>>> map; //I'll assume it is initialized and filled with something.
for (String key : map.keySet()) {
for (HashMap<String, String> map2 : map.get(key)) {
for (String key2 : map2.keySet()) {
//do something
}
}
}
Another approach is to use nested loops through collections:
for (List<HashMap<String, String>> list : map.values()) {
for (HashMap<String, String> map2 : list) {
for (String veryInnerValue : map2.values()) {
//do something
}
}
}
The differ a bit. In case you don't need to know the key of the value, the second is better.
If you need to know key and value for each map :
Map<String, List<HashMap<String, String>>> myMap = new HashMap<String, List<HashMap<String, String>>>();
// loop for first map
for (Entry<String, List<HashMap<String, String>>> myMapEntry : myMap.entrySet()) {
System.out.println("myMap entry key" + myMapEntry.getKey() + ", key ");
// loop for list (value of entry)
for (HashMap<String, String> valueOfList : myMapEntry.getValue()) {
// loop for second map
for (Entry<String, String> entryOfMapOfList : valueOfList.entrySet()) {
System.out.println("key " + entryOfMapOfList.getKey() + " value " + entryOfMapOfList.getValue());
}
}
}

Hashmap's getValue returns Object

I've got the following data structure:
CFU66=[{Bild1=CFU6606}, {Bild2=CFU6603}, {Bild3=CFU6605}, {Bild4=CFU6601}, {Bild5=CFU6602}]
Structure: Hashmap_1(String Key, List(Hashmap_2(String Key, String Value)))
I'm trying to access the values from Hashmap_2:
// for each Hashmap_1 entry
for (Map.Entry<String, List> csvDictEntry : csvDict.entrySet()) {
// for each List in entry.getValue
for (List<HashMap> hashList : csvDictEntry.getValue()) {
// for each Hashmap_2 in List
for (HashMap<String, String> hashListDict : hashList) {
// for each entry in Hashmap_2 print Value
for (Map.Entry<String, String> entry :hashListDict.entrySet()){
System.out.println(entry.getValue());
}
}
}
}
The compiler gives the message, that csvDictEntry.getValue() in the second for-loop returns a Object instead of a Hashmap. Why?
However, I'm pretty new to Java and I'm sure there is a more convenient way to do this.
this
for (Map.Entry<String, List> csvDictEntry : csvDict.entrySet()) {
should be
for (Map.Entry<String, List<Map<String, String>>> csvDictEntry : csvDict.entrySet()) {
Just write all your types
Map<String, List<HashMap<String, String>>> csvDict = null;
for (Map.Entry<String, List<HashMap<String, String>>> csvDictEntry : csvDict.entrySet()) {
// for each List in entry.getValue
for (HashMap<String, String> hashList : csvDictEntry.getValue()) {
// for each Hashmap_2 in List
for (Map.Entry<String, String> entry : hashList.entrySet()) {
System.out.println(entry.getValue());
}
}
}
And also you have extra for loop.
Based on
for (Map.Entry<String, List> csvDictEntry : csvDict.entrySet()) {
I assume that type of csvDict is
Map<String, List> csvDict = ...;
where based on your data example CFU66=[{Bild1=CFU6606}, {Bild2=CFU6603}, {Bild3=CFU6605}, {Bild4=CFU6601}, {Bild5=CFU6602}]
it should be
Map<String, List<Map<String,String>>> csvDict = ...;
Problem with your reference type is that List is raw-type, which means that its actual type is unknown (it can store any kind of Objects) so Object is type which compiler assumes when you are trying to iterate over such list so when normally we would expect
for (Type t : List<Type>)
for raw type we are getting
for (Object o : rawList)
Other problem is way you are iterating because even if we change your reference to proper type
for (List<HashMap> hashList : csvDictEntry.getValue())
will not compile because getValue() returns List<HashMap> so your loop would iterate over HashMaps, not List of HashMaps so it should be
for (HashMap hashList : csvDictEntry.getValue())
Hint: try to avoid concrete types in generics, use parent type if it is possible, like interface or abstract type. This will allow you later easily changing actual type, for instance from HashMap to LinkedHashMap.
So your iteration should look like
for (Map.Entry<String, List<Map<String, String>>> csvDictEntry : csvDict.entrySet()) {
// for each Map in List stored as value
for (Map<String, String> hashListDict : csvDictEntry.getValue()) {
// for each entry in hmap_2 print Value
for (Map.Entry<String, String> entry : hashListDict.entrySet()) {
System.out.println(entry.getValue());
}
}
}
DEMO
BTW in Java 8 your code could be simplified to
csvDict.entrySet().stream()
.flatMap(m -> m.getValue().stream())
.flatMap(m -> m.values().stream())
.forEach(System.out::println);
As indicated by others you forgot to use <> after 'List' on your first line of code. Therefore, the compiler doesn't know what kind of elements are in those lists.
Also you're making it unnecessarily complex by iterating over entrySet when you're only interested in the values.
Map has 3 functions to iterate:
keySet() - if you're only interested in the keys
values() - if you're only interested in the values
entrySet() - if you're interested in both
So in your case...
for (Map<String,String> map : csvDict.values()) {
for (String value : map.values()) {
System.out.println(value);
}
}
Your problem is the first for,what you are currently doing is retrieving a generic list,instead of a particular list.
The declaration of entry set is:
Set<Map.Entry<K,V>> <------- entrySet()
Returns a Set view of the mappings contained in this map.
so you should reconsider chaging your first for to:
for (Map.Entry<String, List<HashMap>> csvDictEntry : csvDict.entrySet()) {

Map same key and value using google collections

I need to validate if map (String to String) entry doesn't contain same key and value pair (case-insensitive). For example -
("hello", "helLo") // is not a valid entry
I was wondering if Google collection's Iterable combined with Predicates some how could solve this problem easily.
Yes I could have simple iterator for entries to do it myself, but thinking of any thing already up.
Looking for something in-lined with Iterables.tryFind(fromToMaster, Predicates.isEqualEntry(IGNORE_CASE)).isPresent()
If you want to use guava, you can use the Maps utils, specifically the filterEntries function.
An example to filter only entries where the key does not equal the value (ignoring the case) could look like this
Map<String, String> map = new HashMap<>();
map.put("hello", "helLo");
map.put("Foo", "bar");
Map<String, String> filtered = Maps.filterEntries(map, new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return !input.getKey().equalsIgnoreCase(input.getValue());
}
});
System.out.println(filtered); // will print {Foo=bar}
However there is no default Predicate in guava's Predicates I know of that does what you want.
Addition:
If you want a validation mechanism without creating a new map, you can use Iterables and the any method to iterate over the entry set of the map. To make the condition more readable I would assign the predicate to a variable or a member field of the class you are working in.
Predicate<Map.Entry<String, String>> keyEqualsValueIgnoreCase = new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return input.getKey().equalsIgnoreCase(input.getValue());
}
};
if (Iterables.any(map.entrySet(), keyEqualsValueIgnoreCase)) {
throw new IllegalStateException();
}
or if you need the entry, you can use the Iterables#tryFind method and use the returned Optional
Optional<Map.Entry<String, String>> invalid = Iterables.tryFind(map.entrySet(), keyEqualsValueIgnoreCase);
if(invalid.isPresent()) {
throw new IllegalStateException("Invalid entry " + invalid.get());
}

How to change HashMap<K, V> value type from Object to String?

What is the easiest/best way to convert
Map<String, Object>
to
HashMap<String, String>
The API I am using has methods that return a Map but it would be easier if I didn't have to cast the Object to a String each time.
Also, is this even worth doing? Would a HashMap be faster/more efficient than a Map?
I'm assuming I'll have to loop through the original Map and copy the values to the new HashMap.
Thanks in advance!
You can use the constructor as others mentioned:
Map<String, String> newMap = new HashMap(oldMap);
This will only work however if you know that the Objects in question are really Strings.
but there is something I should mention:
Do not confuse interfaces with classes. Map is just an interface; a contract which contains only definitions. A class on the other hand is a concrete implementation of an interface. So it does not make any difference in terms of perfomrance if you use the Map interface or its runtime type (HashMap). It can make a difference however if you swap the implementations (to TreeMap for example).
Edit:
Here is the verbose solution which is liked by EE guys (no casting/rawtypes warning involved):
public class MapConverter {
public Map<String, String> convert(Map<String, Object> oldMap) {
Map<String, String> ret = new HashMap<String, String>();
for (String key : oldMap.keySet()) {
ret.put(key, oldMap.get(key).toString());
}
return ret;
}
}
Using the copy constructor on raw types works:
HashMap<String, String> hashMap = new HashMap(map);
However, the solution is ugly as the type system is ignored.
EDIT1:
When you execute
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("Bla", new Object());
HashMap<String, String> hashMap = new HashMap(map);
System.out.println(hashMap.get("Bla").getClass());
}
you get the class cast exception:
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
It is thrown when "System.out.println(hashMap.get("Bla").getClass());" is executed.
Consequently, the casts are actually delayed.
EDIT2:
You can avoid the copy with
HashMap<String, String> hashMap = (HashMap)map;
However, the problem remains the same as the following code shows:
public static void main(String[] args) throws IllegalArgumentException,
InterruptedException, IOException {
HashMap<String, Object> oldMap = new HashMap<String, Object>();
oldMap.put("Bla", new Object());
HashMap<String, String> hashMap = (HashMap)oldMap;
System.out.println(hashMap.get("Bla").getClass());
}
It behaves like the other example above in EDIT1.
EDIT3:
What about using a lambda?
Map<String, Object> map = new HashMap<String, Object>();
// 1
final Stream<Map.Entry<String, Object>> entries = map.entrySet()
.stream();
final Function<Map.Entry<String, Object>, String> keyMapper = (
Map.Entry<String, Object> entry) -> entry.getKey();
final Function<Map.Entry<String, Object>, String> valueMapper = (
Map.Entry<String, Object> entry) -> {
final Object value = entry.getValue();
if (value instanceof String) {
return (String) value;
} else {
throw new ClassCastException("Value '" + value + "' of key '"
+ entry.getKey() + "' cannot be cast from type "
+ ((value != null) ? value.getClass().getName() : null)
+ " to type " + String.class.getName());
}
};
final BinaryOperator<String> duplicateHandler = (key1, key2) -> {
throw new IllegalStateException(String.format("Duplicate key %s",
key1));
};
final HashMap<String, String> hashMap = entries.collect(Collectors
.toMap(keyMapper, valueMapper, duplicateHandler, HashMap::new));
System.out.println(hashMap);
If map only has string-to-string entries, it will copy them all.
E.g. Insert
map.put("aKey", "aValue");
at comment 1. It will print
{aKey=aValue}
which is fine.
If you have at least one string-to-non-string entry in your map, copying will fail.
E.g. Insert
map.put("aKey", 42);
at comment 1. It will print
Exception in thread "main" java.lang.ClassCastException: Value '42' of key ' aKey' cannot be cast from type java.lang.Integer to type java.lang.String
at ...
which shows the string-to-non-string entry.
I know this solution is not so simple but it is safe.
If you know the types of key and value (like <String, String>), you can just cast the whole map:
Map<String, String> newMap = (HashMap<String, String>)oldMap;
If you need a separate Map instance, you can use the constructor of HashMap like this:
HashMap<String, String> = new HashMap<String, String>((HashMap<String, String>) oldMap);

Categories