I'm trying to wrap my head around the basics of Meander, replicating things I use frequently in Clojure. I've managed to get my data into the shape [{:key1 [val1]} {:key1 [val2]} {:key2 [val3]} ...]
, and I want to group it so that it looks like [{:key [val1 val2]} {:key2 [val3]}...]
. Not really sure where to look or if this even makes sense to try to do in Meander.
(defn rule* [x]
(let [x* (m/rewrite x
[[{?k ?v} & ?rest] #{{?k [& ?vs]} ^& ?rest-set}]
[?rest #{{?k [?v & ?vs]} ^& ?rest-set}]
[[{?k ?v} & ?rest] ?rest-set]
[?rest #{{?k [?v]} ^& ?rest-set}]
[[] ?rest-set]
?rest-set
?x
?x)]
(if (= x x*)
x
(recur x*))))
(defn rule [x]
(rule* [x #{}]))
(rule '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}])
;; =>
#{{:key1 [[val2] [val1]]} {:key2 [[val3]]}}
Awesome, thanks! I'm kind of glad it needed something like that, I thought I was missing something super basic 🙂
Grouping is something we want to make easier.
Cleaning it up…
It would be nice if it was more visually obvious what was going on there, I'm not sure the meander version here is better than plain old clj.
I've been haunted by group-by
for a long time (I don't think I've ever wanted an end result that looked like what it gives you), so this is pretty exciting
@snoe Yeah that’s fair. I’ve been pretty happy to recommend people use group-by
.
I've tossing around in my head something like memory variables but for maps instead of vectors. But haven't come up with anything concrete or good yet.
on the other hand if you guys do figure out some way to make that more clear, I would love to stop using group-by
Or some way to tell a variable how it aggregates results. So memory variables are conj. But you might want a sum or an assoc. Or whatever.
(defn rule* [x]
(let [x* (m/rewrite x
{:in [{?k [!vs ...] & ?rest-1} & ?tail]
:out {?k [!vs ...] & ?rest-2}}
{:in [?rest-1 & ?tail]
:out {?k [!vs ...] & ?rest-2}}
{:in [{?k [!vs ...] & ?rest-1} & ?tail]
:out ?m}
{:in [?rest-1 & ?tail]
:out {?k [!vs ...] & ?m}}
{:in [{} & ?tail]
:out ?m}
{:in ?tail
:out ?m}
{:in []
:out ?m}
?m
?x
?x)]
(if (= x x*)
x
(recur x*))))
(defn rule [x]
;; Make a "state" for processing.
(rule* {:in x, :out {}}))
(rule '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}])
;; =>
{:key1 [val2 val1], :key2 [val3]}
AFAICT this isn’t a group-by
though right?
Its like a group-by
plus some post op.
yeah, I think that's the point group-by
is never the end result
Right.
This should work (I think).
(m/rewrite (group-by key (mapcat identity '[{:key1 [val1]} {:key1 [val2]} {:key2 [val3]}]))
{?k [[_ [!vs ...]] ...] & (m/cata ?rest)}
{?k [!vs ...] & ?rest}
{?k ?v & (m/cata ?rest)}
{?k ?v & ?rest})
Second clause to handle other stuff.
When I first sat down with meander, my naive attempt /hope was that something like this might work.
(m/rewrite
[{:key1 [1]} {:key1 [2]} {:key2 [3]}]
[{[!k !v] ...} ...]
{[!k !v] ... ::m/conflict into})
;=> {:key1 [1 2] :key2 [3]}
Maybe I should add that map-of
operator eh?
There’s also “tricks” like
(m/rewrite
[{:key1 [1]} {:key1 [2]} {:key2 [3]}]
[(m/seqable [!k !v] ...) ...]
{& [[!k !v] ...] ::m/conflict into})
;; =>
{:key1 [2], :key2 [3], :meander.epsilon/conflict into}
The {& [[k v] ...]}
bit I need to jot down in the docs.
Cause &
on the right compiles to an into
so you can do this.
The reason I haven’t made much of a mention of it is because, you should be able to write a similar thing on the left.
But doing that is not as simple.
The conflict idea is interesting @snoe. Might have to think about trying out something similar with defsyntax
I think I’d be fine with adding that map-of
operator. I probably should have added it a while back when I brought it up.
Thoughts on this case (m/match {:foo {:a 1, :b 2}} {?a {:a (m/some _)}, ?b {:b (m/some _)}} :ok)
?
Here's a version of dealing with group-by that's a little more obvious (to me, at least) - my actual data has a slightly different shape than above. This gets me from [{:word "discovery", :strokes ["SKOEFR"]} {:word "discovery", :strokes ["SKOEUFR"]} ...]
to ({:word "discovery", :strokes [["SKOEFR"] ["SKOEUFR"] ...]} ...)
(m/search (group-by :word ms)
(m/scan [?word [{:strokes !stroke} ...]])
{:word ?word
:strokes !stroke})
This seems fine to me. Its easy to see what you’re doing though I don’t think you need to bind ?a
or ?b
(you could use _
) but maybe :ok
is just a temporary right side.
@shaun-mahood Did you want the output to :strokes
to be like that?
Not sure if you wanted the result to be flat.
(m/scan [?word [{:strokes [!strokes ...]} ...])
@noprompt Yeah, the different strokes can contain multiple strings in them as well so I need them as vectors for this one.
Gotcha, well that’s definitely easy to understand.
If its possible for :strokes
to be absent you can change the value to be (m/or nil !stroke)
.
Ooh, that will be handy for some of the other things I need to do
When using m/or
with search
, FYI, it will search each branch.
Its like conde
pretty much.
From miniKanren.
In this particular case, ?a and ?b are usually different, but we have some cases where they are the same.
I apparently gave a bad example. It should be m/search.
Ahh, cool.
In any case, I'm not convinced it should be different, I just wondered.
oh, and we do need the bindings...
(m/search {:k {:a 1, :b 2}} {?a {:a (m/some _)} ?b {:b (m/some _)}} [?a ?b]) ;=> ()
Is this the expectation?