This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-07-24
Channels
- # babashka (19)
- # beginners (43)
- # calva (10)
- # clj-kondo (3)
- # cljsrn (8)
- # clojure (106)
- # clojure-europe (8)
- # clojure-hungary (5)
- # clojure-nl (1)
- # clojure-uk (1)
- # clojurescript (14)
- # core-typed (1)
- # graalvm (2)
- # graphql (1)
- # malli (2)
- # membrane (9)
- # observability (2)
- # off-topic (66)
- # polylith (3)
- # practicalli (3)
- # re-frame (17)
- # reagent (3)
- # remote-jobs (7)
- # rewrite-clj (17)
- # sci (29)
- # shadow-cljs (45)
- # sql (5)
- # tools-deps (15)
- # vim (8)
@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/
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.
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.
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)
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.
Meanwhile, I found some solutions at gist: https://gist.github.com/ericnormand/9d0e28695e84efc27c36970ae1462b9c, but they don't use "iteration".
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).
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.
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?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.
Of course, the gist does a whole lot more and for a simple title-case implementation, you can write much simpler code.
I would suggest split
-> map
-> join
as a more idiomatic solution.
(defn title-case-idiomatic
[str]
(clojure.string/join " " (map #(clojure.string/capitalize %)
(clojure.string/split str #" +"))))
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)))
In fact the Python-equivalent iteration version is the hardest one to code, I leave it for tomorrow :(
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.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))
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.
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
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?
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.
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.
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)))
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?
Thank you! I realize now I also used some
wrong
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?Iāve also tried this:
(hash-map
(interleave
(all-markdown-for-path knowledge-graph-path)
(vec (map fs/read-all-lines (all-markdown-for-path knowledge-graph-path)))))
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ā
try
(zipmap (all-markdown-for-path knowledge-graph-path)
(map fs/read-all-lines (all-markdown-for-path knowledge-graph-path)))
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
good question. the 3 arg version of into takes to
, xform
, and from
and it was treating your sequence of keys as a transducer
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))))
you can add a value to a map by conj
ing a key-value pair
> (conj {} [:a 1])
{:a 1}
so to use into
with a map, the goal is to provide it with a sequence of key-value pairs
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"}