Unflatten a HashMap of values - java

I currently have a Map of key value pairs in the format of
a.b.c: value1
e.f: value2
g: [
g.h: nested_value1
g.i: nested_value2
]
and I need to 'unflatten' this to a new Map in a nested structure -
a:
b:
c: value1
e:
f: value2
g: [
h: nested_value1
i: nested_value2
]
My current attempt doesn't get very far, and throws a ConcurrentModificationException
private static Map<String, Object> unflatten(Map<String, Object> flattened) {
Map<String, Object> unflattened = new HashMap<>();
for (String key : flattened.keySet()) {
doUnflatten(flattened, unflattened, key, flattened.get(key));
}
return unflattened;
}
private static Map<String, Object> doUnflatten(
Map<String, Object> flattened,
Map<String, Object> unflattened,
String key,
Object value) {
String[] parts = StringUtils.split(key, '.');
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
Object current = flattened.get(part);
if (i == (parts.length - 1)) {
unflattened.put(part, value);
} else if (current == null) {
if ((current = unflattened.get(part)) == null) {
current = new HashMap<>();
}
unflattened.put(part, current);
unflattened = (Map<String, Object>) current;
} else if (current instanceof Map) {
unflattened.put(part, current);
unflattened = (Map<String, Object>) current;
}
}
return unflattened;
}
Am I missing something obvious here? One solution is to use a library like JsonFlattener - the only issue is this would involve converting back and forward between JSON alot.
Edit: Thanks for the pointers - I am half way there, one thing I forgot to mention was it also needs to unflatten a collection of HashMaps

Your error comes because you iterate the key set and then change the map, not through the iterator.
The iterators returned by all of this class's "collection view
methods" are fail-fast: if the map is structurally modified at any
time after the iterator is created, in any way except through the
iterator's own remove method, the iterator will throw a
ConcurrentModificationException. Thus, in the face of concurrent
modification, the iterator fails quickly and cleanly, rather than
risking arbitrary, non-deterministic behavior at an undetermined time
in the future.
You could get around this by using a new map.

The problem with your implementation is that you are writing the output into the same Map that you use for the input, which causes ConcurrentModificationException.
Implementation becomes straightforward with a separate Map for output:
Map<String,Object> unflattened = new HashMap<>();
for (Map.Entry<String,Object> e : flattened.entrySet()) {
String[] parts = StringUtils.split(e.getKey(), ".");
// Find the map to be used as a destination for put(...)
Map<String,Object> dest = unflattened;
for (int i = 0 ; i != parts.length-1 ; i++) {
Object tmp = dest.get(parts[i]);
if (tmp == null) {
// We did not see this branch yet
Map<String,Object> next = new HashMap<>();
dest.put(parts[i], next);
dest = next;
continue;
}
if (!(temp instanceof Map)) {
throw new IllegalStateException();
}
dest = (Map<String,Object>)temp;
}
// Put the entry into the destination Map<>
dest.put(parts[parts.length-1], e.getValue());
}
Note that the process of "unflattening" may fail when the initial map describes an inconsistent hierarchy, for example, one with a branch and a leaf having the same name:
"a.b.c" -> "x" // OK: "a.b" is a branch
"a.b.d" -> "y" // OK: "a.b" is a branch
"a.b" -> "z" // Error: "a.b" is a leaf

Create a new Map instance for your result instead of attempting to reuse the current one. Also, send in the map value, so it doesn't need to be extracted:
private static Map<String, Object> unflatten(Map<String, Object> flattened) {
Map<String, Object> unflattened = new HashMap<>();
for (String key : flattened.keySet()) {
doUnflatten(unflattened, key, flattened.get(key));
}
return unflattened;
}
This also prevents the original keys from being present in the resulting map.
The above also requires a slight rewrite of the doUnflatten method:
private static void doUnflatten(Map<String, Object> current, String key,
Object originalValue) {
String[] parts = StringUtils.split(key, ".");
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i == (parts.length - 1)) {
current.put(part, originalValue);
return;
}
Map<String, Object> nestedMap = (Map<String, Object>) current.get(part);
if (nestedMap == null) {
nestedMap = new HashMap<>();
current.put(part, nestedMap);
}
current = nestedMap;
}
}
Couple of notes: There's no need to return the map from the method. Divide the loop into two distinct cases: Either the value should be written to the map, or a nested map should be created or retrieved.

The simplest solution is to replace line
for (String key : flattened.keySet()) {
to
for (String key : new ArrayList<>(flattened.keySet())) {
but for large data amount it can be not very effective from performance perspective.

Related

How to delete entry in Java TreeMap?

I am making a method, which takes a provided TreeMap, removes entries where the key is a multiple of keyFilter and the value contains the valueFilter character, and then returns the resulting TreeMap.
This is what I have so far:
public static TreeMap<Integer, String> filterTreeMap(
TreeMap<Integer, String> map, int keyFilter, char valueFilter) {
for (Map.Entry<Integer, String> entry : map.entrySet()) {
int mapKey = entry.getKey();
String mapValue = entry.getValue();
if (mapKey%keyFilter == 0 && mapValue.indexOf(valueFilter) != -1) {
map.remove(mapKey);
}
}
return map;
}
However, under the if condition where I want to delete the entries, I don't know how to delete entries in tree map. As far as I know, there is no existing method that I can use?
Use an Iterator. As the Iterator.remove() Javadoc notes
The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
Something like
public static TreeMap<Integer, String> filterTreeMap(TreeMap<Integer, String> map,
int keyFilter, char valueFilter) {
Iterator<Map.Entry<Integer, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<Integer, String> entry = iter.next();
int mapKey = entry.getKey();
String mapValue = entry.getValue();
if (mapKey % keyFilter == 0 && mapValue.indexOf(valueFilter) != -1) {
iter.remove();
}
}
return map;
}
It is possible to apply method removeIf to the entry set.
default boolean removeIf(Predicate<? super E> filter)
Removes all of the elements of this collection that satisfy the given predicate. Errors or runtime exceptions thrown during iteration or by the predicate are relayed to the caller.
Implementation Requirements:
The default implementation traverses all elements of the collection using its iterator(). Each matching element is removed using Iterator.remove(). If the collection's iterator does not support removal then an UnsupportedOperationException will be thrown on the first matching element.
Then the method filterTreeMap may have void return type because the input map is modified and this change will be "visible" outside this method.
public static void filterTreeMap(
TreeMap<Integer, String> map, int keyFilter, char valueFilter) {
map.entrySet().removeIf(e ->
e.getKey() % keyFilter == 0
&& e.getValue().indexOf(valueFilter) != -1
);
}
Keys on the map are unique. So, find that keys, and then remove them form the map.
public static TreeMap<Integer, String> filterTreeMap(TreeMap<Integer, String> map,
int keyFilter, char valueFilter) {
Set<Integer> keysToRemove = map.entrySet().stream()
.filter(kv -> kv.getKey() % keyFilter == 0 && kv.getValue().indexOf(valueFilter) != -1) // can be Predicate parameter
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
keysToRemove.forEach(map::remove);
return map; // keep in mind, map is modified here. You might want to return a new map instead
}
Iterate over a copy and you can add/remove just fine:
for (Map.Entry<Integer, String> entry : new LinkedHashMap<Integer,String> (map).entrySet()) {
int mapKey = entry.getKey();
String mapValue = entry.getValue();
if (mapKey%keyFilter == 0 && mapValue.indexOf(valueFilter) != -1) {
map.remove(mapKey);
}
}
It's not even any more lines of code, because the copy is made in-line via the copy constructor. LinkedHashMap was chosen to preserve iteration order (if that matters).

How to loop though List<Pair>> iterator

I have a method that loops through a map containing a <Key, <List<Pair>>. How would I loop through this and get all results? As this list contains multiple currencies. I don't want the different type of currency amounts to add together. My attempt is below, it seems to not be picking up all the results
Assuming you have a Map which contains all of your data you could do something like this:
Iterator<Entry<Date, List<Pair<BigDecimal, Currency>>>> itr = source.entrySet().iterator();
Now that you have this Iterator, you can convert it to an Element iterator:
Iterator<Element> eItr = new Iterator<Element> {
int index = 0;
Iterator<Entry<Date, List<Pair<BigDecimal, Currency>>>> itr = source.entrySet().iterator();
Entry<Date, List<Pair<BigDecimal, Currency>>> current = itr.hasNext()? itr.next() : null;
#Override
public boolean hasNext() {
return current != null;
}
#Override
public Element next() {
try {
return new Element(current.getKey(), current.getValue().get(index).getFirst(), current.getValue().get(index).getSeccond());
} finally {
index++;
if (index >= current.getValue().size()) {
index = 0;
current = itr.hasNext()? itr.next() : null;
}
}
}
}
Map<Date, List<Pair<BigDecimal, Currency>>> map = ...
map.forEach((date, list) -> {
list.forEach(pair -> {
// business logic here
});
});
This will loop through the map, and in turn loop through every list. It is unclear what you are trying to achieve, but hopefully this will point you in the right direction.
EDIT (without Java 8)
for (Map.Entry<Date, List<Pair<BigDecimal, Currency>>> entry : map.entrySet()) {
Date date = entry.getKey();
List<Pair<BigDecimal, Currency>> list = entry.getValue();
for (Pair<BigDecimal, Currency> pair : list) {
BigDecimal bd = pair.getKey();
Currency currency = pair.getValue();
//business logic here
}
}
It's slightly less elegant (I guess that's a reason to upgrade to Java 8), but functionally equivalent to the first solution (with slight scope/variable name differences). the for (T t : Iterable<T>) syntax was introduced in Java 5 with the Iterable interface, so this should work for practically everyone.
EDIT (If specifically using Iterator)
while (source.hasNext()) {
Map.Entry<Date, List<Pair<BigDecimal, Currency>>> entry = source.next();
Date date = entry.getKey();
List<Pair<BigDecimal, Currency>> list = entry.getValue();
for (Pair<BigDecimal, Currency> pair : list) {
BigDecimal bd = pair.getKey();
Currency currency = pair.getValue();
//business logic here
}
}

How to compare given keys from array list with HashMap keys?

In my WebApplication I have to check many incoming query parameters from the requestBody. In order not to write the same code in every method, I want to write a function that returns a boolean. When all required parameters are received and the values of the entrySet are not null the method should return true (otherwise false), i can use the incoming query parameters later on in the programm.
Therefore I pack all incoming parameters into a HashMap. Additionally I put a specific list into the method, which provides the required parameters(keys) for checking.
Example Map of queryParams:
Map queryParams = new HashMap();
queryParams.put("id", "1");
queryParams.put("name", "Jane");
queryParams.put("lastname", "Doe");
Example Array:
String[] keys = {"id", "name", "lastname"};
Last version of method:
public static Boolean checkRequestParams(Request request, String[] keys) {
Map params = (JsonUtil.fromJson(request.body(), HashMap.class));
Iterator it = params.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
for (int i = 0; i < keys.length; i++) {
if (pair.getKey().equals(keys[i])) {
return true;
}
}
The Array provides the keys which are the QueryParams the client sent. No i want to compare them and check if the keys in the Hashmap equals to the given keys in the array and if the values of the keys in the Map are not null.
I have tried many variations. Either I got nullPointerExceptions or I always got a null return.
I might be wrong, but as I understood you want to do validate the following condition:
The HashMap keys must belong to the following list of keywords {"id", "name", "lastname"}.
No value from the HashMap should be equal to null.
You might use something similar to this:
map.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null)
So we iterate over the entrySet and check if entry key belong to the defined set and if value is not null.
Here is a more detailed example:
Set<String> keys = Set.of("id", "name", "lastname");
Map<String,List<Integer>> map = Map.of("id", List.of(1,2,3), "name", List.of(4,5,6));
map.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null);
Map<String,List<Integer>> map1 = Map.of("id", List.of(1,2,3), "not in the keys", List.of(4,5,6));
map1.entrySet()
.stream()
.allMatch(entry -> keys.contains(entry.getKey()) && entry.getValue() != null);
Please note that I am using collections factory methods to create Map,List and Set which has been added to java-9, but stream api is available since java-8.
As for your code, you will always get true, because as soon as there is an entrySet which satisfies the condition the method will return result.
for (int i = 0; i < keys.length; i++) {
if (pair.getKey().equals(keys[i])) {
return true; // one single match found return true.
}
}
You can try to reverse the condition and return false as soon as there is a mismatch.
for (int i = 0; i < keys.length; i++) {
if (!pair.getKey().equals(keys[i]) || pair.getValue() == null) {
return false; // mismatch found, doesn't need to verify
// remaining pairs.
}
}
return true; // all pairs satisfy the condition.
I hope you find this useful.
Just using vanilla Java you could try something like this.
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ValidatorExample {
public boolean checkRequestParams(Map<String, Object> request, List<String> keys) {
return isEqualCollection(request.keySet(), keys)
&& !containsAnyNull(request.values());
}
private boolean isEqualCollection (Collection<?> a,Collection<?> b){
return a.size() == b.size()
&& a.containsAll(b)
&& b.containsAll(a);
}
private boolean containsAnyNull(Collection<?> collection){
return collection.contains(null);
}
public static void main(String[] args) {
ValidatorExample validatorExample = new ValidatorExample();
List<String> keys = Arrays.asList("id", "name", "lastname");
Map<String, Object> parametersOk = new HashMap<>();
parametersOk.put("id", "idValue");
parametersOk.put("name", "nameValue");
parametersOk.put("lastname", "lastnameValue");
// True expected
System.out.println(validatorExample.checkRequestParams(parametersOk, keys));
Map<String, Object> parametersWithInvalidKey = new HashMap<>();
parametersWithInvalidKey.put("id", "id");
parametersWithInvalidKey.put("name", "nameValue");
parametersWithInvalidKey.put("lastname", "lastnameValue");
parametersWithInvalidKey.put("invalidKey", "invalidKey");
// False expected
System.out.println(validatorExample.checkRequestParams(parametersWithInvalidKey, keys));
Map<String, Object> parametersWithNullValue = new HashMap<>();
parametersWithNullValue.put("id", null);
parametersWithNullValue.put("name", "nameValue");
parametersWithNullValue.put("lastname", "lastnameValue");
// False expected
System.out.println(validatorExample.checkRequestParams(parametersWithNullValue, keys));
}
}
But I would recommend you to use a validation framework if your project allows it for a more accurate validation.
Should not return immediately if a match is found as we want to test 'all required' parameters. Try something like:
String[] keys = {"id, "name", "lastname"};
public static Boolean checkRequestParams(Request request, String[] keys) {
Map params = (JsonUtil.fromJson(request.body(), HashMap.class));
for (int i = 0; i < keys.length; i++) {
Iterator it = params.entrySet().iterator();
boolean found = false;
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if (pair.getKey().equals(keys[i])) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
You are returning true on the first matching key, whereas you want to check whether all keys are present. Further, your code is incomplete, hence, it is impossible to give full diagnostics.
But anyway, there’s no sense in iterating over map here. Just use
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
for(String key: keys) {
if(params.get(key) == null) return false;
}
return true;
}
This will ensure that each key is present and not mapping to null (as “not mapping to null” already implies being present).
When not considering the possibility of an explicit mapping to null, you could check the presence of all keys as simple as
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
return params.keySet().containsAll(Arrays.asList(keys));
}
Alternatively, you could consider a map invalid if any mapped value is null, even if its key is not one of the mandatory keys. Then, it would be as simple as
public static Boolean checkRequestParams(Request request, String[] keys) {
Map<?,?> params = JsonUtil.fromJson(request.body(), HashMap.class);
return params.keySet().containsAll(Arrays.asList(keys))
&& !params.values().contains(null);
}

What is the fastest method to find duplicates from a collection

This is what I have tried and somehow I get the feeling that this is not right or this is not the best performing application, so is there a better way to do the searching and fetching the duplicate values from a Map or as a matter of fact any collection. And a better way to traverse through a collection.
public class SearchDuplicates{
public static void main(String[] args) {
Map<Integer, String> directory=new HashMap<Integer, String>();
Map<Integer, String> repeatedEntries=new HashMap<Integer, String>();
// adding data
directory.put(1,"john");
directory.put(2,"michael");
directory.put(3,"mike");
directory.put(4,"anna");
directory.put(5,"julie");
directory.put(6,"simon");
directory.put(7,"tim");
directory.put(8,"ashley");
directory.put(9,"john");
directory.put(10,"michael");
directory.put(11,"mike");
directory.put(12,"anna");
directory.put(13,"julie");
directory.put(14,"simon");
directory.put(15,"tim");
directory.put(16,"ashley");
for(int i=1;i<=directory.size();i++) {
String result=directory.get(i);
for(int j=1;j<=directory.size();j++) {
if(j!=i && result==directory.get(j) &&j<i) {
repeatedEntries.put(j, result);
}
}
System.out.println(result);
}
for(Entry<Integer, String> entry : repeatedEntries.entrySet()) {
System.out.println("repeated "+entry.getValue());
}
}
}
Any help would be appreciated. Thanks in advance
You can use a Set to determine whether entries are duplicate. Also, repeatedEntries might as well be a Set, since the keys are meaningless:
Map<Integer, String> directory=new HashMap<Integer, String>();
Set<String> repeatedEntries=new HashSet<String>();
Set<String> seen = new HashSet<String>();
// ... initialize directory, then:
for(int j=1;j<=directory.size();j++){
String val = directory.get(j);
if (!seen.add(val)) {
// if add failed, then val was already seen
repeatedEntries.add(val);
}
}
At the cost of extra memory, this does the job in linear time (instead of quadratic time of your current algorithm).
EDIT: Here's a version of the loop that doesn't rely on the keys being consecutive integers starting at 1:
for (String val : directory.values()) {
if (!seen.add(val)) {
// if add failed, then val was already seen
repeatedEntries.add(val);
}
}
That will detect duplicate values for any Map, regardless of the keys.
You can use this to found word count
Map<String, Integer> repeatedEntries = new HashMap<String, Integer>();
for (String w : directory.values()) {
Integer n = repeatedEntries.get(w);
n = (n == null) ? 1 : ++n;
repeatedEntries.put(w, n);
}
and this to print the stats
for (Entry<String, Integer> e : repeatedEntries.entrySet()) {
System.out.println(e);
}
List, Vector have a method contains(Object o) which return Boolean value based either this object is exist in collection or not.
You can use Collection.frequency to find all possible duplicates in any collection using
Collections.frequency(list, "a")
Here is a proper example
Most generic method to find
Set<String> uniqueSet = new HashSet<String>(list);
for (String temp : uniqueSet) {
System.out.println(temp + ": " + Collections.frequency(list, temp));
}
References from above link itself

java iterator in a nested map (Map 2D)

I would like to know: how can I iterate through a 2D map? I have a centralMap:
private final Map<String, Map<String, String>> centralMap =
new HashMap<String, Map<String, String>>();
which contains another map:
Map<String,String> nestedMap = new HashMap<String, String>();
as Value, and the second one is created in the "put" method, so that in the constructor, i have just the centralMap. Now i want to redefine this method and to get complete entry of the map (the 2 keys and the value for each element)
public Iterator<Entry> iterator()
How should I proceed? If possible, how can I remove an element through the iterator without having a problem?
Iterators are meant to operate on a collection, such as the keyset of your first map, or the values (which are a collection of maps) of your nested one. You cannot expect the iterator renove method to understand your complex structure.
I would suggest that you build your own class for this, with your own convenience methods that do what you described.
Also, going on a limb here: make sure you didn't just want to have a multimap. If so, have a look, for example, at guava's HashMultimap
You apply the same procedure as if you were iterating over a single map, you just do it twice:
public void printNestedMap(Map<String, Map<String, String>> map)
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a map
System.out.println("Key1: " + pairs.getKey());
//print the inner Map
printMap((Map<String, String>)pairs.getValue());
it.remove(); // avoids a ConcurrentModificationException
}
}
EDIT
It would actually be better to move the iteration over a single map to a different method to be called in this scenario.
public void printMap(Map<String, String>> map)
{
Iterator it = map.entrySet().iterator();
while(it.hasNext())
{
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a String
System.out.println("Key2: " + pairs.getKey() + " Value2: " + pairs.getValue());
it.remove();
}
}
EDIT 2: Test Program
import java.util.*;
public class TestMap
{
public static void main(String[] args)
{
Map<String, String> innerMap = new HashMap<>();
Map<String, Map<String, String>> outerMap = new HashMap<>();
innerMap.put("Key1", "Val1");
innerMap.put("Key2", "Val2");
innerMap.put("Key3", "Val3");
innerMap.put("Key4", "Val4");
outerMap.put("OuterKey1", innerMap);
printNestedMap(outerMap);
}
public static void printNestedMap(Map<String, Map<String, String>> map)
{
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a map
System.out.println("Key1: " + pairs.getKey());
//print the inner Map
printMap((Map<String, String>)pairs.getValue());
it.remove(); // avoids a ConcurrentModificationException
}
}
public static void printMap(Map<String, String> map)
{
Iterator it = map.entrySet().iterator();
while(it.hasNext())
{
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a String
System.out.println("Key2: " + pairs.getKey() + " Value2: " + pairs.getValue());
it.remove();
}
}
}
Output:
Key1: OuterKey1
Key2: Key2 Value2: Val2
Key2: Key1 Value2: Val1
Key2: Key4 Value2: Val4
Key2: Key3 Value2: Val3
If you want to get Map.Entry elements containing the two keys and the value, it will really be much more natural to create a class Pair<String, String> that combines the two keys in a single element and use that as the key in a single map rather than nesting maps.
If you do this, your main structure will be a Map<Pair<String, String>, String> and using the Map.entrySet() method will give you a Set<Map.Entry<String, String>, String> from which you can get an iterator that gives approximately what you're after.
If you need to have a Map<String, Map<String, String>> for other reasons, it is also possible to convert this into the structure described above by reasonably simple code, and this might be the most sensible way of getting the information out of it.
Edit Note:
The Pair class described above is essentially the same as Map.Entry, so you could avoid creating a new class for the key by building a Map<Map.Entry<String, String>, String>. I think it makes the code a bit less clear, but it can certainly be made functionally equivalent.
Sample Code
In the code below, I have defined the Pair class as an inner static (for real use, you might want to extract as an independent class), and written a conversion that takes a nested map as you describe, converts it to the form I've suggested, and uses an iterator on the entries of the converted map to print the values.
The iterator could of course be used for other things, and the convert method and Pair class are generic.
import java.util.*;
public class TestMap
{
public static void main(String[] args)
{
Map<String, String> innerMap1 = new HashMap<String, String>();
Map<String, String> innerMap2 = new HashMap<String, String>();
Map<String, Map<String, String>> outerMap = new HashMap<String, Map<String, String>>();
innerMap1.put("InnerKey1", "Val1");
innerMap1.put("InnerKey2", "Val2");
innerMap1.put("InnerKey3", "Val3");
innerMap1.put("InnerKey4", "Val4");
innerMap2.put("InnerKey5", "Val5");
innerMap2.put("InnerKey6", "Val6");
innerMap2.put("InnerKey7", "Val7");
innerMap2.put("InnerKey8", "Val8");
outerMap.put("OuterKey1", innerMap1);
outerMap.put("OuterKey2", innerMap2);
Map<Pair<String, String>, String> convertedMap = convert(outerMap);
for (Map.Entry<Pair<String, String>, String> entry: convertedMap.entrySet()) {
System.out.println(String.format("OuterKey: %s, InnerKey: %s, Value: %s",
entry.getKey().getFirst(),
entry.getKey().getSecond(),
entry.getValue()
));
}
}
private static <K1,K2,V> Map<Pair<K1, K2>,V> convert(Map<K1, Map<K2,V>> nestedMap) {
Map<Pair<K1, K2>, V> result = new HashMap<Pair<K1, K2>, V>();
for (Map.Entry<K1, Map<K2, V>> outerEntry: nestedMap.entrySet()) {
final K1 outerKey = outerEntry.getKey();
for (Map.Entry<K2, V> innerEntry: outerEntry.getValue().entrySet()) {
final K2 innerKey = innerEntry.getKey();
final V value = innerEntry.getValue();
result.put(new Pair<K1, K2>(outerKey, innerKey), value);
}
}
return result;
}
public static class Pair<T1, T2> {
private T1 first;
private T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() {
return first;
}
public T2 getSecond() {
return second;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair pair = (Pair) o;
if (first != null ? !first.equals(pair.first) : pair.first != null) return false;
if (second != null ? !second.equals(pair.second) : pair.second != null) return false;
return true;
}
#Override
public int hashCode() {
int result = first != null ? first.hashCode() : 0;
result = 31 * result + (second != null ? second.hashCode() : 0);
return result;
}
}
}
Note on Usage in context:
In your current code, you have a class with a field centralMap which is the map in your old nested form, and an integer counter for the size of the map.
This containing class has a method for adding entries that looks like this:
#Override
public String put(final String row, final String column, final String value) {
/**
* Second map which is contained by centralMap, that contain Strings as Keys
* and Values.
*/
Map<String, String> nestedMap;
if (centralMap.containsKey(row))
nestedMap = centralMap.get(row);
else
nestedMap = new HashMap<String, String>();
if (!nestedMap.containsKey(column))
counter++;
centralMap.put(row, nestedMap);
return nestedMap.put(column, value);
}
If instead of using the nested map at all, you change this field to a map of the suggested form, this method would become a bit simpler:
#Override
public String put(final String row, final String column, final String value) {
Pair<String, String> key = new Pair(row, column);
if (centralMap.contains(key)
counter++;
centralMap.put(key, value);
}
And you actually wouldn't need the counter anymore, as it will always contain the same value as centralMap.size().
Update:
From edits put in yesterday but now deleted, it's now clear to me (from edit history) that you want to build a single iterator that delegates to all the iterators of the map in correct sequence, and returns a simple structure containing both keys and the value.
This is certainly possible, and if I have time later, I might add some sample code for it. As was noted in another response, the iterator.remove() method may be impossible or unnatural.
Meanwhile, your requirements (as noted a comment on the same other response) is rather similar to what is supplied by guava's Table. That's open source, and looking at it may give you ideas. You can download the source for guava here.
Specifically, in guava's StandardTable, there is an inner class CellIterator, which looks like:
private class CellIterator implements Iterator<Cell<R, C, V>> {
final Iterator<Entry<R, Map<C, V>>> rowIterator
= backingMap.entrySet().iterator();
Entry<R, Map<C, V>> rowEntry;
Iterator<Entry<C, V>> columnIterator
= Iterators.emptyModifiableIterator();
#Override public boolean hasNext() {
return rowIterator.hasNext() || columnIterator.hasNext();
}
#Override public Cell<R, C, V> next() {
if (!columnIterator.hasNext()) {
rowEntry = rowIterator.next();
columnIterator = rowEntry.getValue().entrySet().iterator();
}
Entry<C, V> columnEntry = columnIterator.next();
return Tables.immutableCell(
rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue());
}
#Override public void remove() {
columnIterator.remove();
if (rowEntry.getValue().isEmpty()) {
rowIterator.remove();
}
}
}
You can't just copy this code as it depends on other things in guava, but it shows the basic pattern of what you have to do.

Categories