Clojure:如何合并具有相同值的地图向量

3

如何用惯用的方式将一个键值对向量中的值添加到另一个具有相同给定键值的映射向量中,其中映射不具有相同的键名称。例如:

(def v1 
[{:name "name1" :address "address1"} 
{:name "name2" :address "address2"}])

(def v2 
[{:title "name1" :datofBirth "1-1-1971"} 
{:title "name3" :dateOfBirth "2-1-1971"}]) 

合并的结果应该是:
res 
 [{:name "name1" :address "address1" :dateofBirth "1-1-1971"}
{:name "name2" :address "address2" :dateOfBirth nil}]

调用应该像这样:
 (join v1 v2 :name :title)

而正文应该长这样,也许应该使用assoc-in函数

 (assoc-in v1 [(map-where-the-values-are-the-same) :key2] (value-from-the-2nd-map))

同一个向量中是否可能重复使用相同的键?例如,是否可以有多个元素具有:title name1 - guilespi
@GuillermoWinkler 嗯,我不这么认为,但这是可能的...数据来自 API 调用,所以有可能发生。 - Vesna
2个回答

1
对于内连接,您可以使用clojure.set/join:
(clojure.set/join v1 v2 {:name :title})

您的示例结果似乎表明您想执行左连接。如果是这样,您可能需要查看一些现有的Clojure外连接的Stack Overflow问题;例如,我对最近在Clojure中完全外连接两个映射序列问题的答案提供了一个高效的解决方案,可以直接改编为生成左连接。

以下是一种可能的改编方案:

(defn left-join [key-map xs ys]
  (let [kes (seq key-map)
        lks (mapv key kes)
        rks (mapv val kes)
        gxs (group-by #(mapv (fn [k] (get % k)) lks) xs)
        gys (dissoc (group-by #(mapv (fn [v] (get % v)) rks) ys) nil)
        kvs (keys gxs)]
    (persistent!
     (reduce (fn [out kv]
               (let [l (get gxs kv)
                     r (get gys kv)]
                 (if (seq r)
                   (reduce (fn [out m1]
                             (reduce (fn [out m2]
                                       (conj! out (merge m1 m2)))
                                     out
                                     r))
                           out
                           l)
                   (reduce conj! out l))))
             (transient [])
             kvs))))

在REPL中:
(left-join {:name :title} v1 v2)
;= [{:name "name1", :datofBirth "1-1-1971", :title "name1",
     :address "address1"}
    {:name "name2", :address "address2"}]

如果你更愿意从结果映射中dissoc :title,并将缺失的键添加到左侧记录中,而右侧没有相应记录,则可以稍微修改函数或在后处理步骤中执行。


@michal-marzyk 看起来还可以,但它没有给出完整的第一个向量。实际上,这是左外连接而不是内连接。我会看一下你提供的问题。 - Vesna
这里是一种可能的左外连接实现。 - Michał Marczyk

1

最好编写小的函数,然后使用线程宏调用。在下面的代码中查找。

 (def v1 [{:name "name1" :address "address1"} 
     {:name "name2" :address "address2"}
     {:name "name4" :address "address2"}])

  (def v2 [{:title "name1" :datofBirth "1-1-1971"} 

     {:title "name3" :dateOfBirth "2-1-1971"}
     {:title "name4" :dateOfBirth "2-1-1971"}
     ]) 

(defn filter [k1 k2]
  (fn [d1 d2]
    (for [m1 d1
          m2 d2 
      :let [n1 (k1 m1)
            t1 (k2 m2)]
      :when (= n1 t1) ]
    (merge m1 (dissoc m2 k2)))))


(defn data-join [k1]
  (fn [d1 d2]
    (reduce (fn [acc t] 
          (map (fn [v1 v2] 
                (if (= (k1 v1)
                       (k1 v2))
                  v1 v2)) (repeat t) acc))
        d1 d2)))

    (->> v2 
       ((filter :name :title) v1  )
       ((data-join :name) v1))

筛选函数从v2中筛选数据,然后将结果与第一个函数连接。可以使两个函数更小。


这是一个很好的解决方案,我会研究一下,但我更喜欢Michal的,因为函数调用更加简洁。 - Vesna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接