[英]How to map different values from 2 sets in clojure based on unique value
I have a function A which gives the data 我有一个提供数据的函数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)..}
I am using filter functions and map but couldnt get correct code. 我正在使用过滤器功能和地图,但无法获取正确的代码。
Thank you. 谢谢。
UPDATE 2016-9-26 1727: I added a better solution that uses DataScript to do all of the hard work. UPDATE 2016-9-26 1727:我添加了一个更好的解决方案,该解决方案使用DataScript来完成所有艰苦的工作。 Please see the additional solution at the end.
请最后查看其他解决方案。
Here is an answer that works (w/o 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}
It is like making a mini database and asking (sql pseudocode): 这就像制作一个小型数据库并询问(sql伪代码):
select x.value, y.value from
(natural join x, y on A)
If you are doing this alot, you may find that using a real DB like PostgreSQL or Datomic is useful, or for memory-only stuff consider the clojure lib DataScript. 如果您大量这样做,可能会发现使用像PostgreSQL或Datomic这样的真实数据库很有用,或者对于仅用于内存的东西,请考虑使用clojure lib DataScript。
Here is the DataScript answer: 这是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]}
OK, I'm not 100% sure I've grasped your problem but from what you've described you have two lists of arbitrary maps and you're collecting specific elements from all the maps into a list. 好的,我不是100%肯定我已经解决了您的问题,但是根据您的描述,您有两个任意地图列表,并且您正在将所有地图中的特定元素收集到一个列表中。 There's probably a real slick way to do this with one of the merges (merge-fn, maybe?) but using plain old reduce, you could do it like so:
使用其中一种合并(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)))
Let's take a closer look. 让我们仔细看看。 Starting from the end:
从头开始:
(concat a n)
I'm concatenating the lists because you've indicated that they are completely independent lists of maps. 我将这些列表串联起来,因为您已指出它们是完全独立的地图列表。 There's no uniqueness inside of a list so it can't hurt to treat them all as one list.
列表内部没有唯一性,因此将它们全部视为一个列表不会有什么坏处。
{}
I pass in an empty map. 我传了一张空的地图。 We want a map because while building it, we'll need to keep track of where we're putting things using our preferred key.
我们想要一张地图,因为在构建地图时,我们需要使用首选键来跟踪放置物品的位置。 To the reduce, I pass a function:
为了减少,我传递了一个函数:
(fn[acc i]
It takes an accumulator and an item (acc and i, respectively). 它需要一个累加器和一个项目(分别为acc和i)。 We'll pull out the key from i, which is our map:
我们将从地图中的i中拔出密钥:
(let [k (:key i)
I used :key for clarity, but in your example, you'd want obs/A. 为了清楚起见,我使用了:key,但是在您的示例中,您需要使用obs / A。 Then I take the value:
然后我取值:
v (:value i)]
Then I associated the the value with the key in the accumulator by conjing it with whatever is already there: 然后,通过将值与已经存在的任何内容结合在一起,将值与累加器中的键相关联:
(assoc acc k (conj (acc k) v))))
This is a good trick to know: 这是一个很好的技巧:
(conj nil :whatever)
returns 退货
'(whatever)
and: 和:
(conj '(:whatever) :something)
returns: 返回:
'(:whatever :something)
So you don't have to do anything special for the first case. 因此,对于第一种情况,您不必做任何特殊的事情。
When we're all done, we'll have a map with all the associated values, like in my case I did this: 完成所有操作后,我们将获得一个包含所有关联值的地图,就像我这样:
(def a [{:key 1 :value 2}{:key 2 :value 3}])
(def n [{:key 1 :value 3}{:key 2 :value 4}])
So, just the reduce returns: 因此,只是减少收益:
=> {1 (3 2), 2 (4 3)}
We just want the values of the map, so we wrap it all in a vals and voila: 我们只需要地图的值,所以我们将它们全部包装在vals和voila中:
'((3 2) (4 3))
Hope that helps. 希望能有所帮助。
If you know that there will be no items with the same :obs/A
in the same set, you can just concat both sets, group them on :obs/A
and keep values where there are 2 items in a group: 如果您知道在同一集合中没有任何具有相同
: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))
otherwise you would first have to find the :obs/A
values present in both collections, and then find the corresponding values: 否则,您首先必须找到两个集合中都存在的
: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))
Where data
is the combined responses: 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)}
If you want it without the labels use vals
如果不带标签则使用
vals
Using clojure.set : 使用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)))
Answer: ((3.0 4.0) (4.0) (3.0 7.0) (7.0 2.0)) 答案:((3.0 4.0)(4.0)(3.0 7.0)(7.0 2.0))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.