Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring Java Map of Map of Map

Tags:

java

I'm reviewing an old code of an project and got a datastructure as bellow using Map of Map of Map(3-Layered Map):

// data structure
Map<String, Map<String, Map<String, List<String>>>> tagTree 
            = new HashMap<String, Map<String,Map<String,List<String>>>>();

And fetch the values from Map (I think this is the nice part)

// fetch at tag values
List<String> tagList1 = tagTree.get("Java").get("Active").get("Tags");
List<String> tagList2 = tagTree.get("Java").get("Latest").get("SubTags");

Put the values in Map (little bit complex and error-prone)

// put values
Map<String, Map<String, List<String>>> javaLangMap = new HashMap<String, Map<String, List<String>>>();
Map<String, List<String>> javaStatusMap = new HashMap<String, List<String>>();
List<String> javaTagList = new ArrayList<String>();

javaTagList.add("Java-OOP");
javaTagList.add("Java-Variables");
// put tag list
javaStatusMap.put("Tags", javaTagList);
// put status-wise tag
javaLangMap.put("Active", javaStatusMap);
// put language-wise tag
tagTree.put("Java", javaLangMap);

Currently this is serving to maintain following structure

TagLanguage -> TagStatus -> TagType -> TagList

I'm planning to refactor this Map because it's hard to read for other developers.

Please share your Idea How to do it by considering following cases:

  • All four layer may be changed during runtime.
  • All Level should accessible
  • In-memory solution required i.e. dont use Database table hierarchy .
like image 945
mmuzahid Avatar asked Mar 27 '16 07:03

mmuzahid


1 Answers

If you only ever wanted to access the last level of your data structure, you could use a Multimap<Triple<String,String,String>,String>. Multimap<K,V> is a data structure from Guava which basically is a nicer Map<K,Collection<V>>. Triple<L,M,R> is a 3-elements tuple data structure from Apache Commons Lang3 which is Comparable and implements equals.

You could declare your tag tree like this:

Multimap<Triple<String, String, String>, String> tagTree = HashMultimap.create();

And then fill it like this:

tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-OOP");
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-Variables");

Or:

tagTree.putAll(Triple.of("Java", "Active", "Tags"), Arrays.asList("Java-OOP", "Java-Variables"));

And then get your values from it like this:

Set<String> values = tagTree.get(Triple.of("Java", "Active", "Tags"));

Here is another rough solution that may suit you which enables to get with 1, 2 or 3 keys:

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

public class ThreeLevelMap<K1, K2, K3, V> {
    private Map<K1, Map<K2, Multimap<K3, V>>> firstLevelMap = new HashMap<>();
    private Map<Pair<K1, K2>, Multimap<K3, V>> secondLevelMap = new HashMap<>();
    private Multimap<Triple<K1, K2, K3>, V> thirdLevelMap = HashMultimap.create();

    public void put(K1 key1, K2 key2, K3 key3, V value) {
        thirdLevelMap.put(Triple.of(key1, key2, key3), value);

        final Pair<K1, K2> secondLevelKey = Pair.of(key1, key2);
        Multimap<K3, V> secondLevelContainer = secondLevelMap.get(secondLevelKey);
        if (secondLevelContainer == null) {
            secondLevelContainer = HashMultimap.create();
            secondLevelMap.put(secondLevelKey, secondLevelContainer);
        }
        secondLevelContainer.put(key3, value);

        Map<K2, Multimap<K3, V>> firstLevelContainer = firstLevelMap.get(key1);
        if (firstLevelContainer == null) {
            firstLevelContainer = new HashMap<>();
            firstLevelMap.put(key1, firstLevelContainer);
        }
        firstLevelContainer.put(key2, secondLevelContainer);
    }

    public Collection<V> get(K1 key1, K2 key2, K3 key3) {
        return thirdLevelMap.get(Triple.of(key1, key2, key3));
    }

    public Multimap<K3, V> get(K1 key1, K2 key2) {
        return secondLevelMap.get(Pair.of(key1, key2));
    }

    public Map<K2, Multimap<K3, V>> get(K1 key1) {
        return firstLevelMap.get(key1);
    }
}

You can use it this way:

ThreeLevelMap<String, String, String, String> tlm = new ThreeLevelMap<>();
tlm.put("Java", "Active", "Tags", "Java-OOP");
tlm.put("Java", "Active", "Tags", "Java-Variables");

Map<String, Multimap<String, String>> firstLevelMap = tlm.get("Java");
Multimap<String, String> secondLevelMap = tlm.get("Java", "Active");
Collection<String> tags = tlm.get("Java", "Active", "Tags");

I say it is rough because:

  • the maps that the get methods return are modifiable
  • I didn't implement remove methods
  • I didn't test it a lot
like image 187
Valentin Avatar answered Oct 10 '22 17:10

Valentin