meander

All things about https://github.com/noprompt/meander Need help and no one responded? Feel free to ping @U5K8NTHEZ
grounded_sage 2020-02-18T17:09:04.259300Z

I’m a little lost on trying to join two datasets together.

grounded_sage 2020-02-18T17:09:51.259800Z

I seem to always end up messing around with rewrite 😅

noprompt 2020-02-18T17:14:26.263Z

Thats good!

grounded_sage 2020-02-18T17:17:00.265100Z

(m/rewrite {:event event-dataset
            :venue venue-dataset}
  {:event (m/scan {:related-id id?
                   :attendance !attendance
                   :price !price})
   :venue (m/scan {:related-id id? 
                   :capacity !capacity})
  {:id ?id 
   :data [{:capacity !capacity
           :attendance !attendance 
           :price !price} ...]})
I kind of what something like this.

noprompt 2020-02-18T17:18:37.266Z

But you’re getting out :data with singleton values right?

grounded_sage 2020-02-18T17:19:00.266400Z

Where I have two big lists of data that I want to merge together using a common id

noprompt 2020-02-18T17:21:11.266700Z

Gotcha, gimme just a sec.

grounded_sage 2020-02-18T17:21:43.266900Z

Umm…

grounded_sage 2020-02-18T17:21:48.267100Z

Common id’s

grounded_sage 2020-02-18T17:22:21.267800Z

like I have a bunch of say venues and all the events for every venue. Wanting to merge it all into one datastructure.

noprompt 2020-02-18T17:35:26.268600Z

id? in the match pattern should be ?id 🙂

grounded_sage 2020-02-18T17:36:11.269300Z

I hand typed it into slack so that is a typo haha. Kind of a mock out of what I’m trying to do.

noprompt 2020-02-18T17:45:15.270400Z

@grounded_sage What is the kind of relationship? 1-to-1, N-to-1, etc

grounded_sage 2020-02-18T17:52:39.271Z

Hmm it’s kind of hard to describe. I’m trying to create a mock input and my desired output at the moment.

grounded_sage 2020-02-18T17:57:57.271600Z

I’m hoping I haven’t simplified too much for my problem but it’s something like this.

;; Input
  {:people [{:id 1 :name "Bob"}
            {:id 2 :name "Sarah"}]
   :foods [{:id 1 :food "Apple"}
           {:id 1 :food "Banana"}
           {:id 1 :food "Olives"}
           {:id 2 :food "Pears"}
           {:id 2 :food "Grapes"}]}
  
  ;; Output
  {:id 1 {:name "Bob"
          :foods ["Apple" "Banana" "Olives"]}
   :id 2 {:name "Sarah"
          :food ["Pears" "Grapes"]}}

noprompt 2020-02-18T17:58:25.272Z

(me/rewrite '{:event [{:price p1, :related-id 1, :attendance a1}
                      {:price p3, :related-id 2, :attendance a1}
                      {:price p2, :related-id 1, :attendance a1}]
              :venue [{:capacity c1, :related-id 1}
                      {:capacity c2, :related-id 1}]}
  (me/with [%event {:related-id ?id
                    :attendance !attendance
                    :price !price}
            %venue {:related-id ?id
                    :capacity !capacity}]
    {:event [%event . (me/or %event !not-event) ...]
     :venue [(me/or %venue !not-venue) ...]})
  [{:id ?id 
    :data [{:capacity !capacity
            :attendance !attendance 
            :price !price} ...]}
   ;; Recursively collect the rest.
   & (me/cata {:event [!not-event ...]
               :venue [!not-venue ...]})]

  ;; Default to returning and empty vector
  _ [])

grounded_sage 2020-02-18T18:03:55.272800Z

will take me a little to parse what is happening here 😛

noprompt 2020-02-18T18:06:36.273700Z

(me/with [%event {:related-id ?id
                    :attendance !attendance
                    :price !price}
            %venue {:related-id ?id
                    :capacity !capacity}]
    {;; Find the first event and the rest of the matchings events in the vector
     ;; or collect the non-matching venues separately.
     :event [%event . (me/or %event !not-event) ...]
     ;; Find all the matching venues or collect the non-matching
     ;; venues separately.
     :venue [(me/or %venue !not-venue) ...]})

noprompt 2020-02-18T18:07:56.274300Z

;; Rewrite as a vector with the first item holiding our "join"
  ;; object.
  [{:id ?id 
    :data [{:capacity !capacity
            :attendance !attendance 
            :price !price} ...]}
   ;; Recursively rewrite the structure with the non-matching items
   ;; and merge it into the vector.
   & (me/cata {:event [!not-event ...]
               :venue [!not-venue ...]})]

noprompt 2020-02-18T18:09:17.275500Z

When you use cata on the right side of a rewrite rule, will apply substitution to the argument and then recurse on the rewrite.

noprompt 2020-02-18T18:10:50.277200Z

Since both rewrite rules return vectors [p1 & (me/cata p2)] is basically (into [v1] v2) where v1 is the result of the p1 substitution and v2 is the result of the (me/cata p2) substitution.

jimmy 2020-02-18T18:11:34.277300Z

Are the ids in people unique? Could you index by id instead?

{1 {:id 1 :name "Bob"}
2 {:id 2 :name "Sarah"}}
If so you can probably simplify quite a lot.

jimmy 2020-02-18T18:12:57.277500Z

(Maybe not, probably spoke too soon, but something to consider)

jimmy 2020-02-18T18:15:30.277700Z

What I should have said is can you group by food.

(def data
  {:people [{:id 1 :name "Bob"}
            {:id 2 :name "Sarah"}]
   :foods (group-by :id [{:id 1 :food "Apple"}
                         {:id 1 :food "Banana"}
                         {:id 1 :food "Olives"}
                         {:id 2 :food "Pears"}
                         {:id 2 :food "Grapes"}])})


(m/rewrites data
  {:people (m/scan {:id ?id :name ?name})
   :foods {?id [{:food !food} ...]}}
  {:id ?id 
   :name ?name
   :food [!food ...]})

noprompt 2020-02-18T18:16:46.278200Z

IOW this is a reduction.

1👀
noprompt 2020-02-18T18:17:27.278600Z

For the data I pass above (I forgot to paste the result):

[{:id 1,
  :data
  [{:capacity c1, :attendance a1, :price p1}
   {:capacity c2, :attendance a1, :price p2}]}
 {:id 2, :data []}]

grounded_sage 2020-02-18T18:24:47.280500Z

Thanks guys! I’m still trying to grok what is happening and also finding out if my assumptions are right as well. I’ll definitely have an update tomorrow of how I go

grounded_sage 2020-02-18T18:30:31.280900Z

This definitely simplifies things. I am trying to learn Meander so much I’m forgetting I have Clojure 😅

jimmy 2020-02-18T18:46:09.281100Z

Haha, yeah it is an easy trap to fall into. Meander is fantastic, and eventually we should have good answers for things like this. But definitely don't be afraid to do a little massaging to get something in a shape that makes it easy and obvious to work with 🙂

grounded_sage 2020-02-18T19:40:54.284100Z

Worked like a charm! The more I can KISS this the more valuable it is to the company because we still have to onboard people to Clojure. So going crazy with Meander syntax would be even harder for people to contribute.

jimmy 2020-02-18T19:44:19.284300Z

That's great! Definitely understand the need to keep things familiar for new people. There is a real learning curve in thinking more symbolicly about those sorts of complicated transformations.