Home > front end >  data structure for bidirectional mapping
data structure for bidirectional mapping

Time:12-17

What is the best java 9 datastructure for bidirectional mapping? I am not asking for databases, caches etc., but some simple java object

cat <-> katze
dog <-> hund

I want to get [cat, katze] findByTerm1("cat") and findByTerm2("katze")

My first attempt with enum did not work well.

public enum Foo{
Cat("katze")
} 

because I would need to iterate all Enum values to find by "katze"

CodePudding user response:

Have you looked at BidiMap from Apache Collections? https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/BidiMap.html

Works like a map but you can retrieve entries with either the key or value.

        BidiMap<String, String> biDirectionalMap = new DualHashBidiMap<>(
        Map.ofEntries(
            Map.entry("cat", "katze"),
            Map.entry("dog", "hund")
        )
    );
    
    String cat = biDirectionalMap.getKey("katze");
    String katze = biDirectionalMap.get("cat");

Or if you need to have access to everything once you retrieve your object, keep your enum but store the values in maps to find them in O(1) (you basically trade space for time)

enum Foo
{
    Cat("katze"),
    Dog("hund");
    
    private final String translation;
    
    private static final Map<String, Foo> valuesByName = Arrays.stream(values())
        .collect(Collectors.toMap(Foo::name, Function.identity()));

    private static final Map<String, Foo> valuesByTranslation = Arrays.stream(values())
        .collect(Collectors.toMap(Foo::getTranslation, Function.identity()));
            
    public static Foo fromName(String name)
    {
        return valuesByName.get(name);
    }

    public static Foo fromTranslation(String translation)
    {
        return valuesByTranslation.get(translation);
    }

    Foo(String translation)
    {
        this.translation = translation;
    }

    // Getter... etc        
}

CodePudding user response:

datastructure for bidirectional mapping

A Data structure that would probably fulfill your needs is a Bidirectional map. But there's no standard implementation in the JDK.

Several libraries like Guava (BiMap) and Apache Commons (BidiMap) offers their Bidirectional map implementations.

Instead of introducing dependency, you can create your own implementation which would encapsulate a pair of HashMaps (as @Dave Newton has suggested in the comments).

public class BiMap<K, V> {
    private Map<K, V> valueByKey = new HashMap<>();
    private Map<V, K> keyByValue = new HashMap<>();
    
    public void put(K key, V value) {
        valueByKey.put(key, value);
        keyByValue.put(value, key);
    }

    public V getValue(K key) {
        return valueByKey.get(key);
    }

    public K getKey(V value) {
        return keyByValue.get(value);
    }

    public void removeByKey(K key) {
        V toRemove = valueByKey.remove(key);
        keyByValue.remove(toRemove);
    }

    public void removeByValue(V value) {
        K toRemove = keyByValue.remove(value);
        valueByKey.remove(toRemove);
    }

    public void removeIfMatch(K key, V value) { // removes entries only if the given association key/value is correct
        valueByKey.remove(key, value);
        keyByValue.remove(value, key);
    }
}

If you have in mind to associate instances of enum through a Binary map then you can make use of the EnumMap, which is a special purpose implementation of the Map interface which is faster the than the HashMap since it's free from collisions.

Here's how it might be implemented:

public class BiEnumMap<K extends Enum<K>, V extends Enum<V>> {
    private Map<K, V> valueByKey;
    private Map<V, K> keyByValue;

    private BiEnumMap(Map<K, V> valueByKey, Map<V, K> keyByValue) {
        this.valueByKey = valueByKey;
        this.keyByValue = keyByValue;
    }

    public static <K extends Enum<K>, V extends Enum<V>> BiEnumMap<K, V>
    getEmpty(Class<K> kClass, Class<V> vClass) {
    
        return new BiEnumMap<>(new EnumMap<>(kClass), new EnumMap<>(vClass));
    }
    
    public static <K extends Enum<K>, V extends Enum<V>> BiEnumMap<K, V>
    associateAll(Class<K> kClass, Class<V> vClass) {
        
        List<K> enumA = new ArrayList<>(EnumSet.allOf(kClass)); // note EnumSet is ordered
        List<V> enumB = new ArrayList<>(EnumSet.allOf(vClass));
        
        if (enumA.size() != enumB.size()) throw new IllegalStateException();

        Map<K, V> valueByKey = generateEnumMap(enumA, enumB, kClass, vClass);
        Map<V, K> keyByValue = generateEnumMap(enumB, enumA, vClass, kClass);
        
        return new BiEnumMap<>(valueByKey, keyByValue);
    }
    
    private static <K extends Enum<K>, V extends Enum<V>> Map<K, V>
    generateEnumMap(List<K> enumA, List<V> enumB, Class<K> kClass, Class<V> vClass) {
        
        return enumA.stream()
            .collect(Collectors.toMap(
                Function.identity(),
                e -> enumB.get(e.ordinal()),
                (left, right) -> { throw new AssertionError("all keys are expected to be unique"); },
                () -> new EnumMap<>(kClass)
            ));
    }

    // add required behavior delegating to these wrapped maps
}

Usage example

Consider the following enums:

public enum Foo {
    CAT, DOG;
}

public enum Bar {
    KATZE, HUND;
}

main()

public static void main(String[] args) {
    BiEnumMap<Foo, Bar> map = BiEnumMap.associateAll(Foo.class, Bar.class);
    System.out.println(map.getKey(Bar.KATZE));
    System.out.println(map.getValue(Foo.CAT));
}

Output:

CAT
KATZE

CodePudding user response:

we have vars.io

maybe I will go like that:

private static final Map<String, String> RIGHT = HashMap.of("cat", "Katze", "dog", "Hund"); 
private static final Map<String, String> LEFT = RIGHT.map((k, v) -> Tuple(v, k)); 
  • Related