0

I have a function A which gives the data

{{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.

joey
  • 259
  • 1
  • 13
  • I think you may have mis-typed. Did you mean for that second set to start with "id 1" rather than "id 2"? It matters a lot of the sets have all identical keys, or whether the inner maps map exactly or what. That said, if the ID is supposed to be unique, I think your first problem is your structure is wrong. You have a map containing maps, which doesn't work. You should have a map like {1 {:A "11 :value 2.0 :color "yellow"} 2 {A "12".... – user3810626 Sep 26 '16 at 22:31
  • No its the same. I doesnt matter if id is unique or different. I just want the obs/values together for the obs/A matched in both. – joey Sep 26 '16 at 22:40
  • But you do get that what you've put up there has the first map as a key to the second map, right? It should at least be [{id 1...}{id 2...}...with "[" or "'(" and not "{"? It matters a whole lot. – user3810626 Sep 26 '16 at 23:06
  • @user3810626 yeah it is "(". My bad in a hurry. Sorry for that! – joey Sep 26 '16 at 23:10
  • 1
    Have you looked at cc.set/join? http://stackoverflow.com/a/27909263/6264 – Michiel Borkent Sep 27 '16 at 07:23

5 Answers5

0

UPDATE 2016-9-26 1727: I added a better solution that uses DataScript to do all of the hard work. Please see the additional solution at the end.


Here is an answer that works (w/o 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):

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.


Here is the DataScript answer:

(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]}
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48
  • what does keys here refer to (set/intersection (set (keys xa)) (set (keys ya)))) – joey Sep 27 '16 at 00:28
  • It pulls all of the keys out of a map into a sequence: (keys {:a 1 :b 2}) => [:a :b] – Alan Thompson Sep 27 '16 at 00:31
  • Thank you so much!. – joey Sep 27 '16 at 00:45
  • I have one more question!. If I want to group the second elements in the map in specific range groups like (1-5)(6-10) Ex here in this result would me {[7.0 3.0][3.0 4.0]}{[2.0 7.0]} I tried to implement if loop using peek but having an error. – joey Sep 27 '16 at 00:47
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. 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:

    (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). We'll pull out the key from i, which is our map:

 (let [k (:key i)

I used :key for clarity, but in your example, you'd want 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:

'((3 2) (4 3))

Hope that helps.

user3810626
  • 6,798
  • 3
  • 14
  • 19
  • this is wrong, since it would also include values that are present in one of the sets, bud not present in another – leetwinski Sep 27 '16 at 12:27
0

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:

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:

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))
leetwinski
  • 17,408
  • 2
  • 18
  • 42
0

Where data is the combined responses:

(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

mac
  • 9,885
  • 4
  • 36
  • 51
0

Using 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))

rmcv
  • 1,956
  • 1
  • 9
  • 8