简体   繁体   中英

data structure for bidirectional mapping

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"

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        
}

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 HashMap s ( 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 enum s:

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

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)); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM