Skip to content

Instantly share code, notes, and snippets.

@semperos
Created October 4, 2012 18:19
Show Gist options
  • Save semperos/3835417 to your computer and use it in GitHub Desktop.
Save semperos/3835417 to your computer and use it in GitHub Desktop.
Bidirectional Map in Clojure (Christophe Grand)
;; Big thanks to Christophe Grand - https://groups.google.com/d/msg/clojure/L1GiqSyQVVg/m-WJogaqU8sJ
(defprotocol ReversibleMap
(rmap [m]))
(defn- rdissoc [d r v]
(if-let [[_ k] (find r v)] (dissoc d k) d))
(deftype Bimap [^clojure.lang.IPersistentMap direct reverse]
Object
(hashCode [x]
(.hashCode direct))
(equals [x y]
(.equals direct y))
clojure.lang.IPersistentMap
(without [this k]
(Bimap.
(dissoc direct k)
(rdissoc reverse direct k)))
(assocEx [this k v]
(if (or (contains? direct k) (contains? reverse v))
(throw (Exception. "Key or value already present"))
(assoc this k v)))
java.lang.Iterable
(iterator [this]
(.iterator direct))
clojure.lang.Associative
(assoc [this k v]
(Bimap.
(assoc (rdissoc direct reverse v) k v)
(assoc (rdissoc reverse direct k) v k)))
(containsKey [this k]
(contains? direct k))
(entryAt [this k]
(find direct k))
clojure.lang.IPersistentCollection
(cons [this x]
(if-let [[k v] (and (vector? x) x)]
(assoc this k v)
(reduce (fn [m [k v]] (assoc m k v)) this x)))
(empty [this]
(.empty direct))
(equiv [this x]
(.equiv direct x))
clojure.lang.Seqable
(seq [this]
(seq direct))
clojure.lang.ILookup
(valAt [this k else]
(direct k else))
(valAt [this k]
(direct k))
clojure.lang.Counted
(count [this]
(count direct))
ReversibleMap
(rmap [this]
(Bimap. reverse direct)))
(defn bimap [& kvs]
(reduce (fn [m [k v]] (assoc m k v)) (Bimap. {} {}) (partition 2 kvs)))
;; Some quick tests:
;; user=> (assoc (bimap :a 1 :b 2) [:c 3] [:d 4] [:e 5])
;; {[:e 5] nil, [:c 3] [:d 4], :b 2, :a 1}
;; user=> (assoc (bimap :a 1 :b 2) :c 3 :d 4 :e 5)
;; {:e 5, :d 4, :c 3, :b 2, :a 1}
;; user=> (assoc (bimap :a 1 :b 2) :c 3 :d 2 :e 5)
;; {:e 5, :d 2, :c 3, :a 1}
;; user=> (dissoc *1 :d)
;; {:e 5, :c 3, :a 1}
;; user=> (rmap *1)
;; {5 :e, 3 :c, 1 :a}
;; user=> (assoc *1 2 :c)
;; {2 :c, 5 :e, 1 :a}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment