Fork me on GitHub
#beginners
<
2022-02-16
>
quan xing04:02:26

I have a map data, how can I get all of the value with id.

(def group-m {["Project.5655"
                 "Project-name"
                 "address"
                 "jack"
                 "139543188333"]
                [{:id 429520,
                  :identifier "Project.5655",
                  :project_name "Project-name",
                  :task_name "address",
                  :user_name "jack",
                  :phone "139543188333",
                  :vars
                  "{\"max\": true, \"width\": 1594, \"height\": 830, \"isChange\": false, \"isChuShe\": false, \"auxiliary\": null, \"isChongShen\": false, \"isImportant\": false, \"socialLowInvest\": false}"}
                 {:id 429525,
                  :identifier "Project.5655",
                  :project_name "Project-name",
                  :task_name "address",
                  :user_name "jack",
                  :phone "139543188333",
                  :vars
                  "{\"max\": true, \"width\": 1594, \"height\": 830, \"isChange\": false, \"isChuShe\": false, \"auxilia
ry\": null, \"isChongShen\": false, \"isImportant\": false, \"socialLowInvest\": false}"}]})
or how can I translate this data struct to below struct
{["Project.5655"
                 "Project-name"
                 "address"
                 "jack"
                 "139543188333"] [429520,429525]}

seancorfield05:02:18

@imxingquan Took me a while to understand what you're asking. As before, it would really help if you explained/showed what you have tried so we can guide you along -- otherwise it feels like we're just doing your homework.

seancorfield05:02:04

So you have a vector of hash maps (as the value in the group-m hash map). You want to get the value of the :id in each hash map. If you had just this hash map, how would you get the value of the :id key?

{:id 429520,
                  :identifier "Project.5655",
                  :project_name "Project-name",
                  :task_name "address",
                  :user_name "jack",
                  :phone "139543188333",
                  :vars
                  "{\"max\": true, \"width\": 1594, \"height\": 830, \"isChange\": false, \"isChuShe\": false, \"auxiliary\": null, \"isChongShen\": false, \"isImportant\": false, \"socialLowInvest\": false}"}
(how do you get the value of any specific key in any hash map?)

seancorfield05:02:52

Then, since you have a vector of items (hash maps) and you want to apply that same operation to each item in the vector, how would you do that?

seancorfield05:02:28

At that point, you should have code that can be applied to values in your group-m hash map to produce the values you want, so then you need to walk a hash map, producing a new hash map with the same keys as the original but with your code applied to the values.

seancorfield05:02:53

If you're using Clojure 1.11, you could use update-keys for that. Otherwise you probably want reduce-kv:

(reduce-kv (fn [m k v] (assoc m k (my-fn v)) {} group-m)
where my-fn is the code above to apply the element selector to each item in the vector v.

seancorfield05:02:03

Does that make sense @imxingquan?

seancorfield05:02:14

The key is always breaking down the problem into smaller pieces that you already know how to solve.

quan xing06:02:46

thanks @seancorfield . Next time, I will try to make changes according to what you said. I am sorry that SOMETIMES I do not know how to express in English. Now I still need to use translation tools

seancorfield06:02:50

Don't worry about your English, but show the code you've tried, and try to reduce the problem to simpler data structures that make it clear what you're trying to do. For example, if you started with {:a [{:id 1 :b 2} {:id 3 :c 4}]} and said you wanted {:a [1 3]}, that would have made it easier for folks to understand the problem, instead of trying to figure out what parts of the larger data structure are relevant.

seancorfield06:02:14

Had you tried map or reduce on this already?

quan xing06:02:58

I tried do

(def m {:a [{:id 1 :b 2} {:id 3 :c 4}]})

  {:a (vec (map #(:id %) (first (vals m))))}
  ;; => {:a [1 3]}
It turns out to be correct but I don't think I understand it very well

seancorfield07:02:10

That works only because your initial hash map has only one key/value pair. Maps are inherently unordered so you can't guarantee what order keys or vals produces (although they will be consistent with each other):

dev=> (def m {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9})
#'dev/m
dev=> (vals m)
(5 7 3 8 2 4 6 9 1)
dev=> (keys m)
(:e :g :c :h :b :d :f :i :a)
dev=> 

šŸ‘ 2
seancorfield07:02:09

Instead of (vec (map ...)) you could use (mapv ...) which will also avoid creating and then realizing a lazy sequence.

seancorfield07:02:48

Instead of #(:id %) you could use :id -- keywords can be treated as functions when applied to hash maps and they look themselves up in the map.

seancorfield07:02:35

If you know your starting map has only one key/value pair, you might consider:

dev=> (def m {:a [{:id 1 :b 2} {:id 3 :c 4}]})
#'dev/m
dev=> (let [[k v] (first m)]
 #_=>   {k (mapv :id v)})
{:a [1 3]}
dev=> 
(first m) produces a key/value pair and [k v] destructures that so k is bound to the key and v to the value.

seancorfield07:02:43

(it's late here so I'm off for the night -- good luck and I expect others will be around to help long before I'm back at my computer!)

quan xing09:02:35

Thank you very much @seancorfield I got it . you explain very clean. I am very benefit. have a nice day. see you tomorrow!

josef moudrik12:02:34

Hi! I have a list of '(fn arg1 arg2..), what is the "right" way to execute them? Just eval? Can I do this pattern in a more idiomatic way, or am I overthinking it?

(defn run [fns cols keys]
  (->>
    (map list fns cols keys)
    (map eval)))

(run '(get get) '("abcdef" "ABCDEF") '(2 3))
=> (\c \D)

jumar13:02:23

Don't use a quoted list but a vector containing the functions. Then you can apply them directly

Ferdinand Beyer13:02:22

Elaborating on what @U06BE1L6T explained: (run '(get get) '("abcdef" "ABCDEF") '(2 3)) Here you actually donā€™t pass the function get in, but the symbol. This will be resolved in run by eval, but in theory this could resolve to a different function! E.g., when calling run from a different namespace that has (defn get) in it. Easier, if you can:

(defn run [fns cols keys]
  (map (fn [f c k] (f c k)) fns cols keys))

(run [get get] ["abcdef" "ABCDEF"] [2 3])

agile_geek13:02:42

Also using map may not fully realise the sequence of fn's unless the resulting sequence of results is fully realised.

agile_geek13:02:53

Use mapv or rewrite to use a reduce (or use run! if you are simply executing the fn's for their side effects and don't care about the results)

josef moudrik18:02:32

@UBU374R1P & @U06BE1L6T - I do not understand how vector is better than list tho, how come it does not suffer the same issue

josef moudrik18:02:54

anyway, so I suppose that using backtik (to get fully qual name) is better than the list quote, right?

josef moudrik18:02:41

the usecase is that the functions are "getters", and I construct the list based on other data; basically, cols are data, fns are program (constructed on the data) and the keys are state, that changes (e.g. what element do I get; not sure this helps tho

jumar19:02:40

The difference is qouting not the list data structure itself - try (list get get) Vectors are just convenient and prevalent in Clojure code I do not quite understand your use case but it seems to me that you are making it more complicated than necessary

josef moudrik21:02:15

Ok, thx everyone!

Noah Moss15:02:47

Iā€™m a bit of a noob with both spec and schema (Iā€™ve mostly just picked them up by reading examples in code, so my mental model might be off). Is there a way in either or both of spec/schema (preferably both) to model data that can look like either of these:

{1 {:c :d}}

{1 {:a {:c :d}}}

{1 {:a {:c :d}
    :b {:e :f}}}
i.e. the key 1 can have {:c :d} as a direct child, or it can have an intermediate child :a which then has {:c :d} as its child, and it can also have an intermediate child :b with separate data associated with it. (the actual use case is a more complex structure but this is the core issue Iā€™m hung up on)

pavlosmelissinos16:02:34

Are these valid maps?

{1 {:a {:c :d}
    :c {:g :h}}}
{1 {:a {:c :d}
    :b {:e :f}
    :c {:g :h}}}
If not and you have a lot of restrictions like that, spec might not be the right tool for you because maps are open in spec: if a map has more keys than spec knows about, it won't complain by default. You can make it more strict but it kind of goes against spec's principle. No idea about schema, never used it

Noah Moss16:02:26

I think itā€™s OK if those are valid Basically my scenario is that currently, this data can only have the {1 {:c :d}} form, and spec & schema are both used in the codebase to validate this (spec and schema are used separately in different places; yes, itā€™s a bit redundant) Iā€™m trying to migrate this structure eventually to {1 {:a {:c :d}} so that there can be an optional :b key with different data under it. However in the intermediate state, I need to support both the nested and non-nested form, and Iā€™m not sure how to change the spec/schema validators appropriately

Noah Moss16:02:11

But maybe the easiest solution is to temporarily disable or relax the validation during this intermediate phase

pavlosmelissinos16:02:33

If you're using s/keys and :req now, https://clojuredocs.org/clojure.spec.alpha/keys > The :req key vector supports 'and' and 'or' for key groups: > (s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z])

JohnJ16:02:31

(if (nil? x) "" (. x (toString))))

JohnJ16:02:45

Why is toString wrapped in parens?

seancorfield16:02:26

It's optional for single-argument functions in . and -> forms (and a few other threading-like constructs).

seancorfield16:02:53

I prefer it when using -> to draw a distinction between key selection (which I don't wrap in parens) and function calls (which I do) -- so all functions, whether single-argument or multiple arguments, have ( ) around them. But I think I'm kind of unusual.

seancorfield16:02:11

dev=> (let [x [42]] (if (nil? x) "" (. x (toString))))
"[42]"
dev=> (let [x [42]] (if (nil? x) "" (. x toString)))
"[42]"
dev=> (let [x [42]] (if (nil? x) "" (.toString x)))
"[42]"

JohnJ16:02:25

by key selection you mean stuff like (:foo map) ?

Darin Douglass16:02:29

i donā€™t make the key selection v function call distinction, but i agree with the ā€œalways have () in threadsā€ idea

seancorfield16:02:02

@jjaws (-> some-map :foo (even?)) for example

seancorfield16:02:03

I often use -> when navigating into a nested map and maybe applying a function or two: (some-> req :params :id (parse-long)) is an example from our work codebase.

JohnJ16:02:07

thx, didn't know know . had similar semantics to -> in that part

Adir Ohayon16:02:58

Sorry to barge in mid - conversation, please reply when u get the chance I am trying to check if a string starts with double double quotes (for example, ""test"") using the starts-with? function, although to no avail (tried using escape characters and such, but it didn't go well too) Any ideas or other functions I can explore? (I can always resort to regex)

Darin Douglass16:02:32

905 user> (str/starts-with? "\"\"test\"\"" "\"\"")
 906 true
 907 user>

Darin Douglass16:02:42

starts-with? works for me.

Darin Douglass16:02:18

whenever you have double-quotes in a string each " needs to be quoted

Adir Ohayon16:02:01

Ohh so I was definitely missing that Thank you so much for the fast reply, much appreciated! @U02EA2T7FEH

ghadi16:02:31

sorry to barge in with a meta point, but always type out what you did try specifically

1
ā˜ļø 3
ghadi16:02:45

simply copying REPL input/output is usually sufficient (with a description of what you're trying to achieve, which you did say šŸ™‚ )

Scott Starkey22:02:42

Hi there, folks. Iā€™m having a hard time running tests usingĀ `with-redefs`Ā onĀ `char-array`Ā in one of my tests. testing.clj -

(ns testing.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

(defn step1 [stuff]
  (conj [] stuff))

(defn testing-templates [stuff]
  (-> stuff
      step1
      char-array))
testing-test.clj
(ns testing.core-test
  (:require [clojure.test :refer :all]
            [testing.core :refer :all]))

(defn fake-step1 [arg]
  (conj '() (keyword arg)))

(defn fake-char-array [arg]
  (conj arg :char-array))

(deftest testing-templates-test
  (with-redefs [step1 fake-step1
                char-array fake-char-array]
    (is (= '(:abc) (testing-templates "abc"))
        )))
And here are the results:
(run-tests)

Testing testing.core-test

ERROR in (testing-templates-test) (Numbers.java:1368)
expected: (= (quote (:abc)) (testing-templates "abc"))
  actual: java.lang.ClassCastException: clojure.lang.Keyword cannot be cast to java.lang.Character {... ...}
Is it not allowed to use with-redefs on char-array? It could be something dumb on my part, but I hope not! šŸ˜†

āœ… 1
Alex Miller (Clojure team)22:02:57

char-array is part of clojure.core, which is compiled with AOT direct linking, so calls within the already compiled clojure core to char-array will not be affected by with-redefs

Alex Miller (Clojure team)22:02:48

I'm not sure that's the issue here. it might depend on the order of compilation and options here

hiredman22:02:47

with-redefs only changes the value of the var, the metadata is unchanged

hiredman22:02:34

and char-array has an inline arity (sort of like a macro version of the function) attached as metadata, so the compiler will prefer to use that when not used as a higher order function

Alex Miller (Clojure team)22:02:38

oh right, I forgot that one was an inline

ghadi22:02:03

In general with-redefs leads to anti patterns

ghadi22:02:55

Try to pass ordinary parameters and values whenever possible

JohnJ23:02:25

I feel the same way about DI