What would be the best way to implement a bi-directional map in clojure? (By bi-directional map, I mean an associative map which can provide both A->B and B->A access. So in effect, the values themselves would be keys for going in the opposite direction.)
I suppose I could set up two maps, one in each direction, but is there a more idiomatic way of doing this?
I'm interested both in cases where we want a bijection, implying that no two keys could map to the same value, and cases where that condition isn't imposed.
A map is a collection that holds key-value pairs. The values stored in the map can be accessed using the corresponding key. In Clojure there are two types of maps: Hash maps. Sorted maps.
A BiMap (or “bidirectional map”) is a special kind of a map that maintains an inverse view of the map while ensuring that no duplicate values are present and a value can always be used safely to get the key back.
Maps are represented as alternating keys and values surrounded by { and } . When Clojure prints a map at the REPL, it will put `,'s between each key/value pair. These are purely used for readability - commas are treated as whitespace in Clojure. Feel free to use them in cases where they help you!
You could always use a Java library for this, like one of the collections in Apache commons. TreeBidiMap implements java.util.Map
so it's even seq-able without any effort.
user> (def x (org.apache.commons.collections.bidimap.TreeBidiMap.))
#'user/x
user> (.put x :foo :bar)
nil
user> (keys x)
(:foo)
user> (.getKey x :bar)
:foo
user> (:foo x)
:bar
user> (map (fn [[k v]] (str k ", " v)) x)
(":foo, :bar")
Some things won't work though, like assoc
and dissoc
, since they expect persistent collections and TreeBidiMap is mutable.
If you really want to do this in native Clojure, you could use metadata to hold the reverse-direction hash. This is still going to double your memory requirements and double the time for every add and delete, but lookups will be fast enough and at least everything is bundled.
(defn make-bidi []
(with-meta {} {}))
(defn assoc-bidi [h k v]
(vary-meta (assoc h k v)
assoc v k))
(defn dissoc-bidi [h k]
(let [v (h k)]
(vary-meta (dissoc h k)
dissoc v)))
(defn getkey [h v]
((meta h) v))
You'd probably have to implement a bunch of other functions to get full functionality of course. Not sure how feasible this approach is.
user> (def x (assoc-bidi (make-bidi) :foo :bar))
#'user/x
user> (:foo x)
:bar
user> (getkey x :bar)
:foo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With