online - clojure vs scala
Cómo asignar diferentes valores de 2 conjuntos en clojure en función de un valor único (5)
Tengo una función A que da los datos
{{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)..}
Estoy usando las funciones de filtro y el mapa, pero no pude obtener el código correcto.
Gracias.
ACTUALIZACIÓN 2016-9-26 1727: Agregué una mejor solución que usa DataScript para hacer todo el trabajo duro. Por favor, vea la solución adicional al final.
Aquí hay una respuesta que funciona (sin 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}
Es como hacer una mini base de datos y preguntar (pseudocódigo sql):
select x.value, y.value from
(natural join x, y on A)
Si está haciendo esto mucho, puede que le resulte útil usar una base de datos real como PostgreSQL o Datomic, o para cosas solo de memoria, considere el clojure lib DataScript.
Aquí está la respuesta de 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]}
De acuerdo, no estoy 100% seguro de haber entendido tu problema, pero por lo que has descrito, tienes dos listas de mapas arbitrarios y estás recopilando elementos específicos de todos los mapas en una lista. Probablemente exista una manera realmente astuta de hacer esto con una de las fusiones (¿merge-fn, quizás?) Pero usando plain old reduce, podrías hacerlo así:
(vals (reduce
(fn[acc i]
(let [k (:key i)
v (:value i)]
(assoc acc k (conj (acc k) v)))) {} (concat a n)))
Miremos más de cerca. Comenzando desde el final:
(concat a n)
Estoy concatenando las listas porque has indicado que son listas de mapas completamente independientes. No hay exclusividad dentro de una lista, por lo que no tiene sentido tratarlos a todos como una sola lista.
{}
Paso en un mapa vacío. Queremos un mapa porque, al construirlo, tendremos que hacer un seguimiento de dónde colocamos las cosas utilizando nuestra clave preferida. Para reducir, paso una función:
(fn[acc i]
Se necesita un acumulador y un elemento (acc e i, respectivamente). Vamos a sacar la llave de i, que es nuestro mapa:
(let [k (:key i)
Utilicé: clave para mayor claridad, pero en tu ejemplo, querrías obs / A. Entonces tomo el valor:
v (:value i)]
Luego asocié el valor con la clave en el acumulador conjuntándolo con lo que ya está allí:
(assoc acc k (conj (acc k) v))))
Este es un buen truco para saber:
(conj nil :whatever)
devoluciones
''(whatever)
y:
(conj ''(:whatever) :something)
devoluciones:
''(:whatever :something)
Entonces no tienes que hacer nada especial para el primer caso.
Cuando hayamos terminado, tendremos un mapa con todos los valores asociados, como en mi caso hice esto:
(def a [{:key 1 :value 2}{:key 2 :value 3}])
(def n [{:key 1 :value 3}{:key 2 :value 4}])
Entonces, solo los rendimientos reducidos:
=> {1 (3 2), 2 (4 3)}
Solo queremos los valores del mapa, así que lo envolvemos todo en un vals y listo:
''((3 2) (4 3))
Espero que ayude.
Si sabe que no habrá elementos con el mismo :obs/A
en el mismo conjunto, puede concaturar ambos conjuntos, agruparlos en :obs/A
y conservar los valores donde hay 2 elementos en un grupo:
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))
de lo contrario, primero tendría que encontrar los valores :obs/A
presentes en ambas colecciones, y luego encontrar los valores correspondientes:
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))
Donde los data
son las respuestas combinadas:
(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)}
Si lo quiere sin las etiquetas use vals
Usando 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)))
Respuesta: ((3.0 4.0) (4.0) (3.0 7.0) (7.0 2.0))