Fork me on GitHub
#specter
<
2016-08-16
>
conaw06:08:29

What’s the easiest way to dissoc-in, or to transform a value to not being there?

conaw06:08:32

ideally, I’d like to be able to navigate to a value using walker, and then just setval to nothing, not nil, but nothing, is that possible?

yonatanel06:08:06

@conaw: Perhaps navigate one step behind using selected? and then transform with dissoc. Still not elegant though

conaw07:08:58

looks like this is the closest we’ve got right now to what I’m looking for

conaw08:08:36

so, heres where I’m stuck. right now I’m able to filter out a given value from a vector that is nested one level deep, I’m trying to figure out how to do this recursively though so I could go as deep as needed. the following works

(defpathedfn remove-x [x]
  (filterer #(not= x %)))


(defn t3 [v a]
  (select [
           (remove-x a)
           ALL
           (if-path vector?
                    (remove-x a)
                    STAY)] v))

(deftest test1
  (testing "removal"
    (is
     (= [1 2 3 [4]]
        (t3 [1 2 3 [4 5] 5] 5)))))
what I want is (= [1 2 3 [4 [6]]] (t3 [1 2 3 [4 [5 6] 5] 5] 5))

nathanmarz11:08:20

@conaw: here's one way to do it

nathanmarz11:08:31

(defn remove-x [x]
  (fn [v]
    (filterv #(not= x %) v)))

(declarepath AllVectors)
(providepath AllVectors
  (if-path vector?
    (stay-then-continue
      ALL
      AllVectors)))

(transform AllVectors (remove-x 5) [1 2 3 [4 [5 6] 5] 5])

nathanmarz11:08:33

you could also use ALL-ELEM-SEQ from that linked github issue in conjunction with AllVectors

nathanmarz11:08:14

which would look like: (setval [AllVectors ALL-ELEM-SEQ #(= 5 (first %))] nil [1 2 3 [4 [5 6] 5] 5])

joshkh12:08:26

I'm attempting to "shake" a map of all top level keys where where a deeply nested key in each branch matches a predicate. Is that something I would tackle with transform? Or would I use select in combination with filterer?

nathanmarz12:08:28

what's an example of the input/output you're looking for?

joshkh12:08:14

In this map, I only want to keep top level branches where at least one of the maps in :where have an :op value of "="

{:k1 {:where [{:op    "="
               :value "A"}
              {:op    "ignore"
               :value "X"}]}
 :k2 {:where [{:op    "<"
               :value "A"}
              {:op    "lookup"
               :value "C"}]}
 :k3 {:where [{:op    "="
               :value "A"}
              {:op    "lookup"
               :value "E"}]}}

; result

{:k1 {:where [{:op    "="
               :value "A"}
              {:op    "ignore"
               :value "X"}]}
 :k3 {:where [{:op    "="
               :value "A"}
              {:op    "lookup"
               :value "E"}]}}

joshkh12:08:55

(k2 doesn't make the cut)

joshkh12:08:38

I can build a path to :op no problem, but I lose all parent values when using select. I considered using collect-one at the root of the path while applying filterer at the :op level, but I got the impression I was misunderstanding something fundamental. 🙂

nathanmarz12:08:08

(into {} (traverse [ALL (selected? LAST :where ALL :op #(= "=" %))] data))

nathanmarz12:08:18

you can do it with select too but traverse is more efficient

nathanmarz12:08:45

(doesn't create any intermediate sequence)

joshkh12:08:31

I think I overlooked selected? as well.

joshkh12:08:29

In your code response, what's the difference between 1) using filterer , or 2) passing an anonymous function to the selection path, as you did?

nathanmarz12:08:01

filterer navigates you to a new sequence

nathanmarz12:08:13

a function acts as a predicate and stops navigation at that point if the predicate is false

nathanmarz12:08:32

for selected?, it acts as a predicate where "true" is when anything is selected

nathanmarz12:08:51

since filterer doesn't stop navigation, that wouldn't be relevant in this case

joshkh12:08:01

Okay, that helps a lot. Thanks for the explanation and for solving my use case.

nathanmarz12:08:25

this is worth reading through if you haven't already: https://github.com/nathanmarz/specter/wiki/List-of-Navigators

darwin14:08:31

I’m getting "Cannot read property '0' of null” somewhere deep in specter and I wasn’t able to figure out what is going wrong 😞

nathanmarz14:08:47

@darwin: need a reproducible test case

joshkh14:08:09

Is it okay to nest selected? functions? Going back to your (into {} (traverse [ALL (selected? LAST :where ALL :op #(= "=" %))] data)) response, what if I wanted to also restrict the selection to the :where vec having a count greater than one?

joshkh14:08:25

Oh wait, I'd just use a function there as well.

joshkh14:08:17

Oh, double wait, I'm not sure what I just said made sense.

joshkh14:08:26

Okay, I think I got it? Two separate selected? expressions:

(traverse [s/ALL
           (s/selected? s/LAST :where #(< 1 (count %)))
           (s/selected? s/LAST :where s/ALL :op #(= "=" %))] data)

Chris O’Donnell14:08:54

@joshkh: I think you could do it with (traverse [ALL (selected? LAST :where #(< 1 (count %)) ALL :op #(= "=" %))] data)

Chris O’Donnell15:08:57

also, if you're traversing a map, you could use MAP-VALS instead of LAST

joshkh15:08:03

Ah, that's what how I was thinking about it originally, but I wasn't sure how ALL fit into the path after the function. I guess the obvious answer is that it fits in just fine. 🙂

joshkh15:08:46

And thanks for the advice about MAP-VALS. It'll be easier to remember the data structure when I come back to this in the future.

Chris O’Donnell15:08:07

for sure, and it should be a little more efficient, too!

nathanmarz15:08:08

ALL is needed in this case since traverse must emit key/value pairs

Chris O’Donnell15:08:01

good point @nathanmarz. I'm not thinking very clearly today. @joshkh, you can't use MAP-VALS, since MAP-VALS acts the same as [ALL LAST], but you need the ALL outside of selected? and LAST inside it.

richiardiandrea23:08:35

I was having working at a big reduce today and I thought, maybe specter will be able to help me. Basically the atomic input is:

[{:timestamp 14424
  :chat-id 2
  :payload "bla"}
 {:timestamp 14421
  :chat-id 1
  :payload "asd"}
  {:timestamp 14400
  :chat-id 1
  :payload "test"}]
I have a list of those that I want to convert to a map indexed by chat-id but keeping the oldest timestamp in the value, as:
{1 {:timestamp 14000 :messages [the list]}
  2 {:timestamp 14424 :messages [the list]}}
Can you folks give me a hint (without full solution is fine) from where to start exploring a solution with specter?

Chris O’Donnell23:08:09

@richiardiandrea: could you give a small example of input/output?

richiardiandrea23:08:58

@codonnell: thanks for answering, yes of course, is the above not enough? I am worried it can take too much space

Chris O’Donnell23:08:42

@richiardiandrea: Thanks, that clarifies it.

Chris O’Donnell23:08:07

Would you list just be a list of the payloads for all messages with the given chat-id?

richiardiandrea23:08:13

so I need to carry with me the :timestamp

richiardiandrea23:08:48

the calculation on the timestamp is a bit mysterious to me at the moment

Chris O’Donnell23:08:09

Does the list of messages need to be in order of timestamp?

richiardiandrea23:08:12

(specter newbie)

richiardiandrea23:08:19

yes preferably yes