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