Fork me on GitHub
#beginners
<
2022-05-10
>
Miguel08:05:59

How could I got about transforming a map tree such as

{:a {:b {:c nil}
       :d {:f nil}}
   {:e {:g nil}}}
into another map tree such as
{:node :a
   :children [{:node :b
               :children [{:node :c
                           :children []}]}
              {:node :d
               :children [{:node :f
                           :children []}]}]
   {:node :e
    :children [{:node :g
                :children []}]}}
Is there any library that can help transforming one into the other?

delaguardo08:05:26

look at clojure.walk/post-walk it should be easy to write

delaguardo08:05:13

(require '[clojure.walk :as walk])

(defn walker [node]
  (if (and (map? node)
           (not (contains? node :node)))
    (into []
          (map (fn [[k v]]
                 {:node k
                  :children v}))
          node)
    node))

(let [data {:a {:b {:c nil}
                :d {:f nil}}
            :e {:g nil}}]
  (walk/postwalk
   walker
   data))

delaguardo08:05:18

something like this

Miguel08:05:39

Thank you, was having some issues understanding the argument of the postwalk function.

delaguardo08:05:53

it has the amaizing postwalk-demo function that will help you understand postwalk

Miguel09:05:47

Yep that helps a lot, thanks again

zakkor10:05:53

Hi guys! Is there any reason why seq functions like filter, map, etc, take [f coll] as arguments intead of [coll f]? I know the FAQ mentions: > So, sequence functions take their source(s) last, and any other parameters before them, and partial allows for direct parameterization as above. There is a tradition of this in functional languages and Lisps. and indeed, you can only use partial to partially apply the first argument - but another solution could be to just define a partial-last or something that supplies a default value for the last argument instead, and use that for seq functions when you want to parametrize them. If the parameter order was consistently coll first for all functions, then we could use the -> macro to combine any and all sorts of functions, without needing to worry about the positions of the params. This seems like a much bigger win than being able to use partial directly. Is there another reason why this wouldn't have been a good idea?

👀 1
delaguardo10:05:26

thread macro is not the only way to combine sequential functions. They are also transducers if the source is omitted.

zakkor10:05:35

Ah, interesting, I knew I was missing something. So are transducers generally the better way of composing functional pipelines? And can I mix and match transducers with collection functions like get-in?

delaguardo10:05:54

in some cases transducers would be preferred because they do not consume entire collection. https://clojure.org/reference/transducers

pithyless06:05:56

You also have as-> when you need to mix argument order in a threading macro

pithyless06:05:13

Stuart Sierra had some thoughts on why you shouldn't need to mix -> and ->> - https://stuartsierra.com/2018/07/06/threading-with-style

👍 2
oly12:05:20

Wonder if some one can leand me a hand, not sure if I am hitting an issue with my java knowledge here, tried to convert this code to clojure the bit I am unsure about is ICampaignManagementService.class

campaignManagementExampleHelper.CampaignManagementService = new ServiceClient<ICampaignManagementService>(
                        authorizationData, 
                        API_ENVIRONMENT,
                        ICampaignManagementService.class);
If I use this code it appears to work but the .class call is omitted and I get an error later calling a method on the service client which I believe could be due to not calling class
(defn fetch-reporting-service-manager [auth-data]
  (new ServiceClient ^ICampaignManagementService
       auth-data
       environment
       ICampaignManagementService))
If I try to translate it like so it error with this No matching field found: class for class java.lang.Class which I don't 100% understand looks like a method call to me is this the correct translation ?
(defn fetch-reporting-service-manager [auth-data]
  (new ServiceClient ^ICampaignManagementService
       auth-data
       environment
       (.class ICampaignManagementService)))

dorab21:05:51

Just a guess, but try

(ServiceClient. auth-data environment ICampaignManagementService)

dorab21:05:06

The ServiceClient<ICampaignManagementService> is a Java generic and is mainly used for type checking in Java. From a Clojure (and JVM) perspective, it is the same as ServiceClient.

oly07:05:26

okay thanks, this is basically what I am doing (ServiceClient. auth-data environment ICampaignManagementService) it is the dropping of the ICampaignManagementService.class ie the .class part that I am less sure of the implications of doing.

dorab21:05:48

I'm pretty sure that in Clojure, using the class name (e.g. ICampaignManagementService ) refers to the class.

oly12:05:32

okay good to know in case I see it again thanks 🙂

V13:05:54

I have a map "entry" {:a ["b" "c"] :d "e"} of which i want to convert the values of :a into keywords. I tried this

(update entry :a keyword)
but it returns {:a nil :d "e"} instead. I don't understand why though, i thought this would work

oly13:05:06

I believe this is because your basically calling (keyword ["b" "c"]) ie your calling it on a sequence, you probably want to use a map keyword on the value

zakkor13:05:15

(update data :a #(map keyword %))

zakkor13:05:28

something like this?

oly13:05:50

alternatively you may be able to use this [clojure.walk :refer [keywordize-keys]] which i think works on a sequence from memory

oly13:05:25

atually that may be for a hashmap rather than a sequence, go with what @U03EC9KU17H suggested 🙂

V13:05:33

Ah of course. That makes sense. I tried using map before that, but i got an error that "key must be integer". This was due to me forgetting the hashtag infront of map

V13:05:47

Thank you very much

zakkor13:05:56

you could also do (update data :a (partial map keyword)) I think, maybe that's nicer?

zakkor13:05:33

but that turns it into a list (), rather than a vector [], so you can use mapv instead: (update data :a (partial mapv keyword))

Old account14:05:42

Hello! how to convert all keys of a hashmap to strings?

pavlosmelissinos14:05:49

The "easy" answer is clojure.walk/stringify-keys (examples https://clojuredocs.org/clojure.walk/stringify-keys) but the obvious question is why do you need to do that?

👍 1
Eric14:05:57

Do you want a list of just the keys as strings, or the same hash-map, but with the keys as strings instead of whatever they currently are?

Old account14:05:53

I need to have the same hashmap but with the keys of strings

sb14:05:34

what pavlos mentioned that is exactly what you want

(use 'clojure.walk)

(stringify-keys {:a 1 :b 2})
;=> {"a" 1, "b" 2}

Martin Půda14:05:14

(update-keys {:a 1 :b 2} name)
=> {"a" 1, "b" 2}

☝️ 3
Old account14:05:31

@U01RL1YV4P7 what package update-keys is ?

pavlosmelissinos14:05:50

it's in clojure.core but only available after since Clojure 1.11

Old account15:05:47

clojure.walk/stringify-keys could work but I only need keys to be updated in the first level

pavlosmelissinos15:05:32

Alright, if you only care about the top level keys and you have a recent version of Clojure, update-keys is better!

peterh23:05:01

You could also do something like this, if you need to use an older version of Clojure:

(#(zipmap (map name (keys %)) (vals %)) {:a 1 :b 2})
;=> {"a" 1, "b" 2}