Fork me on GitHub
#beginners
<
2021-07-24
>
sandeep11:07:24

@jr0cket can i complete your youtube series in 1 month

practicalli-johnny12:07:51

@spamails29 hmm, there is over 100 hours of video to watch, so I guess it depends how dedicated you are 🙂 If you watch 4 videos a day, then you should get through them all in a month. https://practical.li/#videos-broadcasts I recommend just watching the ones most relevant to you, either from specific playlists or just by the titles in the study group playlist, which includes all the live videos. If you are just starting to learn Clojure, I suggest focusing on the 4Clojure Guides playlist after trying to attempt these challenges at https://4clojure.oxal.org/

practicalli-johnny12:07:56

Feel free to ask any questions about the videos or any of the free books I am developing on the #practicalli channel (or just at-mention me somewhere) Thank you for taking an interest.

🙌 6
❤️ 3
😺 3
whatacold14:07:20

Hi, how do you assign a value to a let binding, say state? I am trying to keep a state for a map #(...) a-str , so that the lambda will run according the to state and update it. I failed to find a solution in book "Living Clojure" and neither with googling.

jaju14:07:44

Let binding or not, the state will need to be a mutable entity that is available to the lambda. So, your lambda will need to refer to this global, mutable thing and 1. Use it 2. Update it in each run. You may need to use an atom Another (presumably better) way is to use reduce (in lieu of map and a mutable state)

whatacold14:07:30

Thanks! I'll look into atom and see if I can figure it out. I'm challenging myself to implement a title-case for a string (like Python's s.title()), is it appropriate to use reduce? I haven't figured how.

whatacold14:07:49

Meanwhile, I found some solutions at gist: https://gist.github.com/ericnormand/9d0e28695e84efc27c36970ae1462b9c, but they don't use "iteration".

whatacold14:07:47

I guess maybe reduce can't help for my case? as what I want (a title case string) is different from my initial value (a state and a initial empty string).

jaju15:07:47

For this problem, IMHO, using map and reduce will lead to convoluted code. 🙂 reduce will be somewhat easier/cleaner relatively speaking, as you can pass the current-state (previous-char, title-cased-string-until-now) and keep updating it. Note that your current-state to reduce is made up of multiple components. The gist represents a more realistic approach in the real-world, but what you are doing is a nice exercise in learning too.

whatacold15:07:01

What I want is something equivalent to below Python code:

def title_case(s):
    "Return a title cased string for s, which only has `[a-zA-Z ]`
    result = []
    prev_whitespace_p = True
    for c in s:
        if ' ' == c:
            result.append(c)
            prev_whitespace_p = True
        else:
            result.append(c.upper() if prev_whitespace_p else c)
            prev_whitespace_p = False
    return ''.join(result)
Is it common or not to write Clojure code like that?

jaju15:07:03

In my experience, this isn’t the common way in Clojure. But this is efficient - does what you need in a single pass, by using a helper mutable state (prev_whitespace_p). If you don’t mind multiple passes, we can avoid this altogether. The gist you pointed to does that. More passes, but more functional.

jaju15:07:41

Of course, the gist does a whole lot more and for a simple title-case implementation, you can write much simpler code.

R.A. Porter15:07:26

I would suggest split -> map -> join as a more idiomatic solution.

whatacold15:07:35

Got it, thank you both!

👍 3
whatacold16:07:05

For exercise purpose, here is my idiomatic version:

whatacold16:07:16

(defn title-case-idiomatic
  [str]
  (clojure.string/join " " (map #(clojure.string/capitalize %)
                                (clojure.string/split str #" +"))))

👍 3
whatacold16:07:39

And my reduce version:

(defn title-case-reduce
  [str]
  (first (reduce #((if (= \space %2)
                     [(str (first %1) %2) true]
                     [(str (first %1) (if (second %1)
                                        (Character/toUpperCase %2)
                                        %2)) false]))
                 ["" true]
                 str)))

whatacold16:07:48

In fact the Python-equivalent iteration version is the hardest one to code, I leave it for tomorrow :(

whatacold03:07:30

Here is my "map" iteration version:

(defn title-case-iteration
  [input-str]
  (let [prev-whitespace-p (atom true)
        result (atom "")]
    (run! #(reset! result
                   (str @result
                        (if @prev-whitespace-p
                          (do (reset! prev-whitespace-p (= \space %))
                              (if (= \space %) % (Character/toUpperCase %)))
                          (do (reset! prev-whitespace-p (= \space %))
                              %))))
          input-str)
    @result))
Over the course, I learned that map returns a lazy seq, so that it won't call the lambda as the seq is consumed by nobody, and run! is perfect for this case.

jaju04:07:46

It’s a nice attempt! Here’s a slightly refactored version of the above for your consideration (I’ve tried to stay true to your flow)

(defn title-case-iteration-2 [input-str]
  (let [prev-whitespace-p (atom true)
        result (atom "")]
    (run! (fn [c]
            (reset! result 
                    (str @result
                         (if @prev-whitespace-p
                           (Character/toUpperCase c)
                           c)))
            (reset! prev-whitespace-p (= \space c)))
          input-str)
    @result))

jaju04:07:04

The latter versions (as opposed to the idiomatic one) preserve the spaces - just to keep that piece of info explicit in the discussion. 🙂 So, there’s a tradeoff.

jaju04:07:17

Here’s a version with reduce and no atom for state - so, a single pass and space-preserving.

(defn title-case-reduce [s]
  (->> s
       (reduce
        (fn [[prev accum] c]
          (cond
            (= :begin prev)
            [nil (Character/toUpperCase c)]
            (= \space c)
            [c (str accum prev)]
            (= \space prev)
            [nil (str accum prev (Character/toUpperCase c))]
            :else
            [nil (str accum c)]))
        [:begin ""])
       second))

;; You can also choose to use clojure.string/capitalize instead of Character/toUpperCase

whatacold04:07:51

Thanks, title-case-iteration-2 is cleaner and more concise. BTW, why do you prefer to use explicit (fn) here to define a lambda? Is it because the function body is too long, so that naming the argument as c is better for reading?

jaju04:07:05

Matter of taste, but I like to use the (fn) version when the lambda tends to grow big/multi-line - when the % is easy to miss.

whatacold06:07:35

https://whatacold.io/blog/2021-07-25-clojure-string-title-case/ I put all these into the above blog post for better reading, thanks so much @U06KGH6LB.

👏 3
🎉 3
jaju07:07:55

Nice! (Also - appreciate the credits! Also, really liked your summarisation of the learning.) Here’s another one if you’d like to use regex (preserves spaces - I am not a regex geek though!)

(defn title-case-regex [s]
  (->> (clojure.string/split s #"(?<=\s)|(?=\s)")
       (map clojure.string/capitalize)
       (apply str)))

whatacold07:07:48

Thanks for another version:thumbsup:

Andrew Berrien16:07:22

In Elixir we have a function Enum.any? which traverses a collectible and returns true when it reaches the first element where a given condition is met, and false if it reaches the end (e.g. Enum.any? [1, 2, 3] &is_even?). I know in clojure we could do something like (some? (some (filter is-even? [1 2 3]))) but I'm wondering is there a more convenient function for this specific case?

tschady16:07:33

user=> (some even? [1 2 3])
true
user=> (some even? [1 5 3])
nil

tschady16:07:40

nil is falsey

Andrew Berrien16:07:28

Thank you! I realize now I also used some wrong

Rob Haisfield23:07:14

Okay so I have two vectors. They both have the same number of elements in them. The first is a vector of strings, and the second is a vector of vectors of strings. I’m trying to get them into a map with the elements in the first vector as keys and the second vector as values. How do I do that? I’ve tried running this function:

(into {}
      (all-markdown-for-path knowledge-graph-path)
      (vec (map fs/read-all-lines (all-markdown-for-path knowledge-graph-path))))
But I end up getting this error message: “Execution error (IllegalArgumentException) at functions-for-processing-notes.core/eval9850 (core.clj:281). Key must be integer” This error message is really confusing to me… can’t keys in a map be of any type?

Rob Haisfield23:07:29

I’ve also tried this:

Rob Haisfield23:07:30

(hash-map
 (interleave
   (all-markdown-for-path knowledge-graph-path)
   (vec (map fs/read-all-lines (all-markdown-for-path knowledge-graph-path)))))

Rob Haisfield23:07:04

But that didn’t work either, even though AFAICT the only thing that should change should be the parentheses at the end into curly brackets? Error message was “No value supplied for key: clojure.lang.LazySeq@fb7ca471”

phronmophobic23:07:56

try

(zipmap (all-markdown-for-path knowledge-graph-path)
        (map fs/read-all-lines (all-markdown-for-path knowledge-graph-path)))

Rob Haisfield23:07:54

Awesome, that worked, thank you! Do you know why the other things I tried didn’t work though? It’s hard for me to tell what those error codes mean and they break my mental model a little bit

phronmophobic23:07:09

good question. the 3 arg version of into takes to, xform, and from and it was treating your sequence of keys as a transducer

phronmophobic23:07:59

if you wanted to use into, you could do something like:

(into {}
      (partition-all 2)
      (interleave
       (all-markdown-for-path knowledge-graph-path)
       (map fs/read-all-lines (all-markdown-for-path knowledge-graph-path))))

phronmophobic23:07:35

you can add a value to a map by conj ing a key-value pair

phronmophobic23:07:03

> (conj {} [:a 1])
{:a 1}

phronmophobic23:07:29

so to use into with a map, the goal is to provide it with a sequence of key-value pairs

phronmophobic23:07:12

to use hashmap, you need to a sequence of key-value arguments rather than one argument that is a sequence of key-value pairs: Eg.

> (hash-map :a 1 :b 2)
{:b 2, :a 1}
> (apply hash-map
   (interleave [1 2 3 4]
               ["a" "b" "c" "d"]))
{1 "a", 4 "d", 3 "c", 2 "b"}

👍 3