【问题标题】:How to map different values from 2 sets in clojure based on unique value如何根据唯一值在 clojure 中映射 2 个集合中的不同值
【发布时间】:2016-09-26 21:11:15
【问题描述】:

我有一个提供数据的函数 A

{{id 1,:obs/A "11", :obs/value 2.0, :obs/color "yellow"}
{id 2,:obs/A "12", :obs/value 4.0, :obs/color "blue"}
{id 3,:obs/A "13", :obs/value 3.0, :obs/color "green"}
{id 3,:obs/A "15", :obs/value 7.0, :obs/color "red"}...}

and a function B which gives the data

{{id 2,:obs/A "11", :obs/value 7.0, :obs/shape "square"}
{id 2,:obs/A "13", :obs/value 4.0, :obs/shape "circle"}
{id 6,:obs/A "15", :obs/value 3.0, :obs/shape "triangle"}...}

I want to map obs/value from both functions which match with same obs/A.

Here the result will be like {(2.0,7.0),(3.0,4.0)..}

我正在使用过滤函数和映射,但无法获得正确的代码。

谢谢。

【问题讨论】:

  • 我想你可能打错了。您的意思是第二组以“id 1”而不是“id 2”开头吗?重要的是很多集合具有所有相同的键,或者内部映射是否准确映射或什么。也就是说,如果 ID 应该是唯一的,我认为你的第一个问题是你的结构是错误的。您有一张包含地图的地图,但它不起作用。你应该有一个像 {1 {:A "11 :value 2.0 :color "yellow"} 2 {A "12"....
  • 不一样。 id 是唯一的还是不同的我都没有关系。我只想将 obs/values 放在一起,以便 obs/A 在两者中都匹配。
  • 但是你确实知道你放在那里的第一张地图是第二张地图的钥匙,对吧?它至少应该是 [{id 1...}{id 2...}...with "[" or "'(" 而不是 "{"?这很重要。
  • @user3810626 是的,它是“(”。我很着急。对不起!
  • 你看过 cc.set/join 吗? stackoverflow.com/a/27909263/6264

标签: clojure


【解决方案1】:

更新 2016-9-26 1727:我添加了一个更好的解决方案,它使用 DataScript 来完成所有的艰苦工作。请参阅最后的附加解决方案。


这是一个有效的答案(没有 DataScript):

(ns clj.core
  (:require [tupelo.core :as t] 
            [clojure.set :as set] ))
(t/refer-tupelo)

(def x
  [ {:id 1,   :obs/A "11",    :obs/value 2.0,    :obs/color "yellow"}
    {:id 2,   :obs/A "12",    :obs/value 4.0,    :obs/color "blue"}
    {:id 3,   :obs/A "13",    :obs/value 3.0,    :obs/color "green"}
    {:id 3,   :obs/A "15",    :obs/value 7.0,    :obs/color "red"} ] )

(def y
  [ {:id 2,   :obs/A "11",    :obs/value 7.0,    :obs/shape "square"}
    {:id 2,   :obs/A "13",    :obs/value 4.0,    :obs/shape "circle"}
    {:id 6,   :obs/A "15",    :obs/value 3.0,    :obs/shape "triangle"} ] )

(newline) (println "x") (pretty x)
(newline) (println "y") (pretty y)

; Note this assumes that :obs/A is unique in each sequence x and y
(def xa (group-by :obs/A x))
(def ya (group-by :obs/A y))
(newline) (println "xa") (pretty xa)
(newline) (println "ya") (pretty ya)

(def common-a (set/intersection (set (keys xa)) (set (keys ya))))
(newline) (spyx common-a)

(def values-map
  (apply glue
    (for [aval common-a]
      { (-> aval xa only :obs/value)
        (-> aval ya only :obs/value) } )))
(newline) (spyx values-map)


> lein run
x
[{:id 1, :obs/A "11", :obs/value 2.0, :obs/color "yellow"}
 {:id 2, :obs/A "12", :obs/value 4.0, :obs/color "blue"}
 {:id 3, :obs/A "13", :obs/value 3.0, :obs/color "green"}
 {:id 3, :obs/A "15", :obs/value 7.0, :obs/color "red"}]

y
[{:id 2, :obs/A "11", :obs/value 7.0, :obs/shape "square"}
 {:id 2, :obs/A "13", :obs/value 4.0, :obs/shape "circle"}
 {:id 6, :obs/A "15", :obs/value 3.0, :obs/shape "triangle"}]

xa
{"11" [{:id 1, :obs/A "11", :obs/value 2.0, :obs/color "yellow"}],
 "12" [{:id 2, :obs/A "12", :obs/value 4.0, :obs/color "blue"}],
 "13" [{:id 3, :obs/A "13", :obs/value 3.0, :obs/color "green"}],
 "15" [{:id 3, :obs/A "15", :obs/value 7.0, :obs/color "red"}]}

ya
{"11" [{:id 2, :obs/A "11", :obs/value 7.0, :obs/shape "square"}],
 "13" [{:id 2, :obs/A "13", :obs/value 4.0, :obs/shape "circle"}],
 "15" [{:id 6, :obs/A "15", :obs/value 3.0, :obs/shape "triangle"}]}

common-a => #{"15" "13" "11"}

values-map => {7.0 3.0, 3.0 4.0, 2.0 7.0}

这就像做一个迷你数据库并询问(sql伪代码):

select x.value, y.value from 
  (natural join x, y on A)

如果您经常这样做,您可能会发现使用真正的数据库(如 PostgreSQL 或 Datomic)很有用,或者对于仅内存的东西,请考虑使用 clojure lib DataScript。


这是 DataScript 的答案:

(ns clj.core
  (:require [tupelo.core :as t] 
            [datascript.core :as d]
            [clojure.set :as set] ))
(t/refer-tupelo)

(def data
  [ {:type :x :local/id 1,   :obs/A "11",    :obs/value 2.0,    :obs/color "yellow"}
    {:type :x :local/id 2,   :obs/A "12",    :obs/value 4.0,    :obs/color "blue"}
    {:type :x :local/id 3,   :obs/A "13",    :obs/value 3.0,    :obs/color "green"}
    {:type :x :local/id 3,   :obs/A "15",    :obs/value 7.0,    :obs/color "red"} 

    {:type :y :local/id 2,   :obs/A "11",    :obs/value 7.0,    :obs/shape "square"}
    {:type :y :local/id 2,   :obs/A "13",    :obs/value 4.0,    :obs/shape "circle"}
    {:type :y :local/id 6,   :obs/A "15",    :obs/value 3.0,    :obs/shape "triangle"} ] )

(def conn (d/create-conn {}))
(d/transact! conn data)

(def labelled-result
  (d/q '[:find ?a ?value1 ?value2
             :where 
               [?ex :type :x] [?ex :obs/A ?a] [?ex :obs/value ?value1]
               [?ey :type :y] [?ey :obs/A ?a] [?ey :obs/value ?value2]
            ] @conn ))
(newline) (println "labelled-result") (pretty labelled-result)

(def unlabelled-result
  (d/q '[:find    ?value1 ?value2
             :where 
               [?ex :type :x] [?ex :obs/A ?a] [?ex :obs/value ?value1]
               [?ey :type :y] [?ey :obs/A ?a] [?ey :obs/value ?value2]
            ] @conn ))
(newline) (println "unlabelled-result") (pretty unlabelled-result)

> lein run

labelled-result
#{["13" 3.0 4.0] ["11" 2.0 7.0] ["15" 7.0 3.0]}

unlabelled-result
#{[3.0 4.0] [2.0 7.0] [7.0 3.0]}

【讨论】:

  • 这里的keys指的是什么(set/intersection(set(keys xa))(set(keys ya))))
  • 它将所有键从映射中提取到一个序列中: (keys {:a 1 :b 2}) => [:a :b]
  • 非常感谢!
  • 我还有一个问题!如果我想将地图中的第二个元素分组到特定范围组中,例如 (1-5)(6-10) Ex here in this result would me {[7.0 3.0][3.0 4.0]}{[2.0 7.0]} I尝试使用 peek 实现 if 循环但出现错误。
【解决方案2】:

好的,我不是 100% 确定我已经掌握了您的问题,但是根据您的描述,您有两个任意地图列表,并且您正在将所有地图中的特定元素收集到一个列表中。可能有一种真正巧妙的方法可以通过其中一个合并(merge-fn,也许?)来做到这一点,但是使用普通的旧 reduce,你可以这样做:

    (vals (reduce 
      (fn[acc i]
        (let [k (:key i)
              v (:value i)]
          (assoc acc k (conj (acc k) v)))) {} (concat a n)))

让我们仔细看看。从头开始:

(concat a n)

我将这些列表连接起来,因为您已经指出它们是完全独立的地图列表。列表内部没有唯一性,因此将它们全部视为一个列表不会有什么坏处。

{}

我传入了一张空地图。我们想要一张地图,因为在构建它时,我们需要使用我们的首选键来跟踪我们将东西放在哪里。对于reduce,我传递了一个函数:

(fn[acc i]

它需要一个累加器和一个项目(分别是 acc 和 i)。我们将从 i 中取出钥匙,也就是我们的地图:

 (let [k (:key i)

为了清楚起见,我使用了 :key,但在您的示例中,您需要 obs/A。然后我取值:

  v (:value i)]

然后我将值与累加器中的键相关联,方法是将它与已经存在的任何内容结合起来:

(assoc acc k (conj (acc k) v))))

这是一个很好的技巧:

(conj nil :whatever)

返回

'(whatever)

和:

(conj '(:whatever) :something)

返回:

'(:whatever :something)

所以你不必为第一种情况做任何特别的事情。

当我们都完成后,我们将拥有一个包含所有关联值的地图,就像在我的例子中我这样做:

(def a [{:key 1 :value 2}{:key 2 :value 3}])
(def n [{:key 1 :value 3}{:key 2 :value 4}])

所以,只有reduce返回:

=> {1 (3 2), 2 (4 3)}

我们只想要地图的值,所以我们将它全部包装在一个 vals 中,瞧:

'((3 2) (4 3))

希望对您有所帮助。

【讨论】:

  • 这是错误的,因为它还包括存在于其中一个集合中的值,而芽不存在于另一个集合中
【解决方案3】:

如果您知道在同一个集合中不会有具有相同 :obs/A 的项目,您可以将两个集合连接起来,将它们分组到 :obs/A 并保留一个组中有 2 个项目的值:

user> (def rel1 #{{:id 1,:obs/A "11", :obs/value 2.0, :obs/color "yellow"}
                  {:id 2,:obs/A "12", :obs/value 4.0, :obs/color "blue"}
                  {:id 3,:obs/A "13", :obs/value 3.0, :obs/color "green"}
                  {:id 3,:obs/A "15", :obs/value 7.0, :obs/color "red"}})
#'user/rel1

user> (def rel2 #{{:id 2,:obs/A "11", :obs/value 7.0, :obs/shape "square"}
                  {:id 2,:obs/A "13", :obs/value 4.0, :obs/shape "circle"}
                  {:id 6,:obs/A "15", :obs/value 3.0, :obs/shape "triangle"}})
#'user/rel2

user> (keep (fn [[_ v]] (when (> (count v) 1) (map :obs/value v)))
            (group-by :obs/A (concat rel1 rel2)))
;;=> ((3.0 4.0) (7.0 3.0) (2.0 7.0))

否则,您必须首先找到两个集合中存在的:obs/A 值,然后找到相应的值:

user> (let [r1 (group-by :obs/A rel1)
            r2 (group-by :obs/A rel2)
            ;; keysets intersection
            ks (keep (set (keys r1)) (keys r2))]
        (map #(map :obs/value (concat (r1 %) (r2 %)))
             ks))
;;=> ((2.0 7.0) (7.0 3.0) (3.0 4.0))

【讨论】:

    【解决方案4】:

    data 是组合响应:

    (into {} (for [[k v] (group-by :obs/A data)]
               (if (= 2 (count v))
                 [k (map :obs/value v)])))
    
    => {"11" (2.0 7.0), "13" (3.0 4.0), "15" (7.0 3.0)}
    

    如果你想要它没有标签,请使用vals

    【讨论】:

      【解决方案5】:

      使用clojure.set

      (def a #{{:id 1,:obs/A "11", :obs/value 2.0, :obs/color "yellow"}
              {:id 2,:obs/A "12", :obs/value 4.0, :obs/color "blue"}
              {:id 3,:obs/A "13", :obs/value 3.0, :obs/color "green"}
              {:id 3,:obs/A "15", :obs/value 7.0, :obs/color "red"}})
      
      (def b #{{:id 2,:obs/A "11", :obs/value 7.0, :obs/shape "square"}
              {:id 2,:obs/A "13", :obs/value 4.0, :obs/shape "circle"}
              {:id 6,:obs/A "15", :obs/value 3.0, :obs/shape "triangle"}})
      
      (use 'clojure.set)
      
      (->> [a b]
          (map #(index % [:obs/A]))
          (apply merge-with union)
          vals
          (map (partial map :obs/value)))
      

      答案:((3.0 4.0) (4.0) (3.0 7.0) (7.0 2.0))

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-16
        • 1970-01-01
        • 2020-02-18
        • 1970-01-01
        • 1970-01-01
        • 2012-06-28
        相关资源
        最近更新 更多