Skip to content

Instantly share code, notes, and snippets.

@ataggart
Last active April 12, 2022 20:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ataggart/d00cfde376476c9e495882a60deba904 to your computer and use it in GitHub Desktop.
Save ataggart/d00cfde376476c9e495882a60deba904 to your computer and use it in GitHub Desktop.
left-join analog of clojure.set/join
(defn left-join
"When passed 2 rels, returns the rel corresponding to the natural
left-join. When passed an additional keymap, joins on the corresponding
keys."
([xrel yrel]
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel)))
(set (keys (first yrel))))
idx (index yrel ks)]
(reduce (fn [ret x]
(if-let [found (idx (select-keys x ks))]
(reduce #(conj %1 (merge %2 x)) ret found)
(conj ret x)))
#{} xrel))
xrel))
([xrel yrel km]
(let [idx (index yrel (vals km))]
(reduce (fn [ret x]
(if-let [found (idx (rename-keys (select-keys x (keys km)) km))]
(reduce #(conj %1 (merge %2 x)) ret found)
(conj ret x)))
#{} xrel)))
@jumarko
Copy link

jumarko commented Feb 21, 2021

At line 12, I think it should be (merge x %2) instead of (merge %2 x) (as in the original implementation).
The reason for that is that you want values from yrel to "override" values in xrel.

Adapted example:

(defn left-join
  "Returns the rel corresponding to the naturaljoin of two relations (sets).
  Unlike `clojure.set/join` always expects an additional keymap (that's what we want in most cases)
  joining on the corresponding keys.

  Adapted from https://gist.github.com/ataggart/d00cfde376476c9e495882a60deba904"
  ([xrel yrel km]
   (let [yrel-idx (set/index yrel (vals km))]
     (reduce (fn [ret x]
               (if-let [found-in-yrel (get yrel-idx (set/rename-keys (select-keys x (keys km)) km))]
                 (reduce #(conj %1 (merge x %2)) ret found-in-yrel)
                 ;; the element is not in `yrel` so just include it as is - no merging needed
                 (conj ret x)))
             #{}
             xrel))))

(comment

  (def xs #{{:a 11 :b 1 :c 1 :d 4}
            {:a 2 :b 12 :c 2 :d 6}
            {:a 3 :b 3 :c 3 :d 8 :f 42}
            {:a 4 :b 4 :c 4 :d 4}})

  (def ys #{{:a 11 :b 11 :c 11 :e 5}
            {:a 12 :b 11 :c 12 :e 3}
            {:a 3 :b 3 :c 3 :e 7 }})

  (left-join xs (set/rename ys {:a :aa :c :yc}) {:a :aa})
  ;; => #{{:a 4, :b 4, :c 4, :d 4} ; this is only in `xs`
  ;;      {:a 11, :b 11, :c 1, :d 4, :e 5, :aa 11, :yc 11} ; value of `b` is overriden by `ys` (it's 1 in `xs`)
  ;;      {:a 3, :b 3, :c 3, :d 8, :f 42, :e 7, :aa 3, :yc 3}
  ;;      {:a 2, :b 12, :c 2, :d 6}}


,)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment