I’m learning Clojure now, I’m coming from imperative programming, so I have problems understanding execution flow of that code:
(defn chop-chop [coll]
(let [x (partition-by identity coll)]
(map list (map (comp str first) x)
(map count x))))
=> (chop-chop "aaaabbbcca")
=> (("a" 4) ("b" 3) ("c" 2) ("a" 1))
I understand that we:
create here function chop-chop with parameter coll
then we apply function partition-by for coll, but I’m not sure what identity is
next we bound result of previous operation to x
but I can’t understand the next lines of code:
(map list (map (comp str first) x)
(map count x))))
Could someone explain to me step-by-step execution flow of that program?
Thanks a lot!
The jist
This example:
Takes a collection (in this case, a string)
Gets the groups of repeating letters
Maps over the groups to get the representative letter
Maps over the groups to get the count of each group
Maps over #3 and #4 to get a key-value tuple, the letter and the number of times it repeats (for this instance, not all time).
The jist+
On your four bullets:
Yup, it's a single-arity function defined with the defn macro
Yup, and identity returns the argument
;; Possible values: 1, :a, #(+ 1 2), {:a [1 2]}
(identity 1) ;;=> 1
(identity :a) ;;=> :a
(identity #(+ 1 2)) ;;=> #function[boop.core/eval7764/fn--7765]
(identity {:a [1 2]}) ;;=> {:a [1 2]}
(partition-by identity "aaaabbbcca") ;;=> (\a \a \a \a) (\b \b \b) (\c \c) (\a))
Just in case you don't understand partition-by, it creates a new group each time the value of the function changes. #(< 3 %) will be false for 1, 2. Because the results are identical, they're grouped together. 3 4 5 will have the same result, so they'll be grouped together.
;; f
;; =
;; number?
;; coll
;; [1 2 3 4 5]
;; [:a :b '(:Yo) 3]
;; f1 coll1
(partition-by #(= 3 %) [1 2 3 4 5]) ;;=> ((1 2) (3) (4 5))
;; f1 coll2
(partition-by #(= 3 %) [:a :b '(:Yo) 3]) ;;=> ((:a :b (:Yo)) (3))
;; f2 coll1
(partition-by number? [1 2 3 4 5]) ;;=> ((1 2 3 4 5))
;; f2 coll2
(partition-by number? [:a :b '(:Yo) 3]) ;;=> ((:a :b (:Yo)) (3))
Yes, you bind the previous operation to x. This example maps two maps, so it would've been clearer if they bound the two maps to variables:
(defn chop-chop [coll]
(let [x (partition-by identity coll)
;; Could bind the two maps here
first-letter-map (map (comp str first) x)
repeating-letter-count (map count x)]
;; a map of maps
(map list first-letter-map repeating-letter-count)))
(chop-chop "aaaabbbcca") ;;=> (("a" 4) ("b" 3) ("c" 2) ("a" 1))
On the last bit of code:
Maps can map over one or more collections.
Here's one collection per map:
;; maps
;; (map (comp str first) x)
;; (map count x)
;; coll
;; [["woo" "yes"] ["hello" "world"]]
;; ((\a \a \a \a) (\b \b \b) (\c \c) (\a)), the result of (partition-by identity "aaaabbbcca")
;; m1 c1
(map (comp str first) [["woo" "yes"] ["hello" "world"]]) ;;=> ("woo" "hello")
;; m1 c2
(map (comp str first) '((\a \a \a \a) (\b \b \b) (\c \c) (\a))) ;;=> ("a" "b" "c" "a")
;; m2 c1
(map count [["woo" "yes"] ["hello" "world"]]) ;;=> (2 2)
;; m2 c2
(map count '((\a \a \a \a) (\b \b \b) (\c \c) (\a))) ;;=> (4 3 2 1)
Here's two collections per map:
;; function
;; #(str (first %1) (first %2))
;; #(list (count %1) (count %2))
;; same colls, but passed in at the same time
;; [["woo" "yes"] ["hello" "world"]]
;; ((\a \a \a \a) (\b \b \b) (\c \c) (\a))
(def c1 [["woo" "yes"] ["hello" "world"]])
(def c2 '((\a \a \a \a) (\b \b \b) (\c \c) (\a)))
(map #(str (first %1) (first %2)) c1 c2) ;;=> ("wooa" "hellob")
(map #(list (count %1) (count %2)) c2 c1) ;;=> ((4 2) (3 2))
You should also understand comp:
;; comp vs. personall const
;; (comp str first)
;; #(str (first %))
;; seq
;; [\a "Wow"]
;; [132 :a]
;; c1 s1
((comp str first) [\a "Wow"]) ;;=> "a"
;; c2 s1
(#(str (first %)) [\a "Wow"]) ;;=> "a"
;; c1 s2
((comp str first) [132 :a]) ;;=> "132"
;; c2 s2
(#(str (first %)) [132 :a]) ;;=> "132"
Welcome to the Clojure community!
partition-by applies function identity for each value in col, and it splits col each time it returns a new value.
For example:
user=> (partition-by #(= 3 %) [1 2 3 4 5])
((1 2) (3) (4 5))
It applies partition with the function (= 3 %), so it splits the col in 3 parts, (1 2) are false (3) is true and (4 5) is false.
You are using identity as function, aa function that returns its argument.
(partition-by identity "aaaabbbcca") returns ((\a \a \a \a) (\b \b \b) (\c \c) (\a)).
Next you are doing (map count x), so you are counting each list of your x, returning: (4 3 2 1).
(map (comp str first) x) returns you a string with first character of each list in x: ("a" "b" "c" "a").
Finally you are doing:
(map list '("a" "b" "c" "a")
'(4 3 2 1))))
And that creates a list combining the two lists:
For example, with the first element of each list makes:
(list "a" 4)-> ("a" 4)
Doing this with the four elements:
(("a" 4) ("b" 3) ("c" 2) ("a" 1))
Related
Hi am learning clojure and trying to find the index of the vowels in a string here is what I tried
(def vowels [\a \e \i \o \u \y])
(let [word-index (interleave "aaded" (range))
indexs (for [ [x i] (vector word-index)
:when (some #{x} vowels)]
[i] )]
(seq indexs))
But this is giving me index "0" or nill what am doing wrong.
> (def vowels #{\a \e \i \o \u})
> (filter some? (map #(when (vowels %1) %2) "aaded" (range)))
(0 1 3)
You need to form the input correctly for the for comprehension:
(let [word-index (interleave "aaded" (range))
indexs (for [[x i] (partition 2 word-index)
:when (some #{x} vowels)]
i)]
(prn (seq indexs)))
;; => (0 1 3)
interleave will give a lazy sequence when we mapped that sequence to the vector of for loop, I think I missed the indexes. So changed the implementation as below.
(let [word-index (zipmap (range) "aaded")
indexs (for [ [i x] word-index
:when (some #{x} vowels)]
[i] )
]
(flatten indexs)
)
Which is working fine, if anyone has better implementation please share. It will be helpful for me thanks.
With every iteration of the for function, the same hash-set is formed repeatedly. So it's better to define it in the let block. Also, we can use the hash-set directly as a function and we don't need the some function for the same.
(let [word-index (zipmap (range) "aaded")
vowels-hash (into #{} [\a \e \i \o \u \y])
indexs (for [[i x] word-index
:when (vowels-hash x)]
[i])]
(flatten indexs))
a bit different approach with regex:
for all indices:
user> (let [m (re-matcher #"[aeiou]" "banedif")]
(take-while identity (repeatedly #(when (re-find m) (.start m)))))
;;=> (1 3 5)
for single index:
user> (let [m (re-matcher #"[aeiou]" "bfsendf")]
(when (re-find m) (.start m)))
;;=> 3
user> (let [m (re-matcher #"[aeiou]" "bndf")]
(when (re-find m) (.start m)))
;;=> nil
#jas has got this nailed down already. Adding my own to provide some comments on what happens in intermediary steps.
Use sets to check for membership. Then the question "is this a vowel?" will be fast.
(def vowels (set "aeiouy"))
vowels
;; => #{\a \e \i \o \u \y}
We can filter out the vowels, then get just the indexes
(defn vowel-indices-1 [word]
(->> (map vector (range) word) ; ([0 \h] [1 \e] [2 \l] ...)
(filter (fn [[_ character]] ; ([1 \e] [4 \o])
(contains? vowels character)))
(map first))) ; (1 4)
(vowel-indices-1 "hello!")
;; => (1 4)
... or we can go for a slightly more fancy with the :when keyword (didn't know about that, thanks!), in the style that you started!
(defn vowel-indices-2 [word]
(for [[i ch] (map vector (range) word)
:when (contains? vowels ch)]
i))
(vowel-indices-2 "hello!")
;; => (1 4)
I’m having trouble writing an elegant drop-last-by or butlast-by function.
(drop-last-by odd? [2 1 9 4 7 7 3]) ; => (2 1 9 4)
(drop-last-by odd? [2 4]) ; => (2 4)
(drop-last-by odd? [9]) ; => ()
What I have so far works but seems a little clumsy and I wonder if it can be done in just two or three lines.
(defn drop-last-by [pred coll]
(let [p (partition-by pred coll)]
(apply concat (if (and (seq p) (pred (first (last p))))
(butlast p)
p))))
Since drop-while already does basically what you need, and since your current solution is already not lazy, I'd write drop-last-by like this:
(defn drop-last-by [pred coll]
(reverse (drop-while pred (reverse coll))))
The version below is lazy to the degree permitted by the problem specification:
any elements that do not satisfy the predicate are immediately passed through without reading any additional elements from the source;
any elements that do satisfy the predicate are passed through as soon as an element that does not satisfy the predicate is read in from the source;
any elements that satisfy the predicate and are not followed by further elements that do not satisfy the predicate are dropped.
Additionally, it can be used as a (stateful) transducer; indeed the lazy seq version is implemented in terms of the transducer and clojure.core/sequence.
(defn drop-last-by
([pred]
(fn [rf]
(let [xs (volatile! [])]
(fn
([] (rf))
([result] (rf result))
([result input]
(if-not (pred input)
(do
(reduce rf result #xs)
(vreset! xs [])
(rf result input))
(do
(vswap! xs conj input)
result)))))))
([pred coll]
(sequence (drop-last-by pred) coll)))
At the REPL:
(drop-last-by odd? [2 1 9 4 7 7 3])
;= (2 1 9 4)
(drop-last-by odd? [2 4])
;= (2 4)
(drop-last-by odd? [9])
;= ()
Composed with other transducers:
(into []
(comp (drop-while even?)
(drop-last-by odd?)
(map #(str "foo " %)))
[0 1 2 3 4 5])
;= ["foo 1" "foo 2" "foo 3" "foo 4"]
What is the difference between map-indexed and keep-indexed?
map-indexed is like map, except that the index of each element in the coll is passed as the first arg to the function that map-indexed takes, and the element is passed as the second arg to the function.
So
(map-indexed + [1 2 3 4]) ;=> ((+ 0 1) (+ 1 2) (+ 2 3) (+ 3 4)) => (1 3 5 7)
keep-indexed works the same way as map-indexed with the difference that if (f index value) returns nil, it is not included in the resulting seq.
So for example:
(keep-indexed #(and %1 %2) [1 2 3 nil 4]) ;;=> (1 2 3 4)
You can think of keep-indexed as map-indexed wrapped in filter as follows:
(filter (complement nil?) (map-indexed f coll))
Keep-indexed will keep the result of fn if result is not nil
(keep-indexed #(if (odd? %1) %2) [:a :b :c :d :e])
;;(:b :d)
map-indexed will keep all result of applying fn to coll regardless return value is nil or not
(map-indexed #(if (odd? %1) %2) [:a :b :c :d :e])
;; (nil :b nil :d nil)
What I want to do is like following.
(def mystream (stream (range 100)))
(take 3 mystream)
;=> (0 1 2)
(take 3 mystream)
;=> (3 4 5)
(first (drop 1 mystream))
;=> 7
The stream function make sequence side-effectfull like io stream.
I think this is almost impossible.
Here is my attempt.
(defprotocol Stream (first! [this]))
(defn stream [lst]
(let [alst (atom lst)]
(reify Stream
(first! [this]
(let [[fs] #alst]
(swap! alst rest)
fs)))))
(let [mystream (stream (iterate inc 1))]
(map #(if (string? %) (first! mystream) %)
[:a "e" "b" :c "i" :f]))
;=> (:a 1 2 :c 3 :f)
Unfotunately this approach need to implement all function I will use.
Judging by your followup comment to Maurits, you don't need mutation, but rather simply need to emit a new sequence with the elements in the right place.
For example:
(defn replace-when [pred coll replacements]
(lazy-seq
(when (seq coll)
(if (seq replacements)
(if (pred (first coll))
(cons (first replacements)
(replace-when pred (rest coll) (rest replacements)))
(cons (first coll)
(replace-when pred (rest coll) replacements)))
coll))))
user=> (def seq1 [:a :b :c])
#'user/seq1
user=> (def seq2 [:x "i" "u" :y :z "e"])
#'user/seq2
user=> (replace-when string? seq2 seq1)
(:x :a :b :y :z :c)
This won't work with the standard take and drop, but you could quite easily write your own to work on a mutable atom, e.g. you could do something like this:
(def mystream (atom (range 100)))
(defn my-take [n stream]
(let [data #stream
result (take n data)]
(reset! stream (drop n data))
result))
(my-take 3 mystream)
=> (0 1 2)
(my-take 3 mystream)
=> (3 4 5)
Given:
(def my-vec [{:a "foo" :b 10} {:a "bar" :b 13} {:a "baz" :b 7}])
How could iterate over each element to print that element's :a and the sum of all :b's to that point? That is:
"foo" 10
"bar" 23
"baz" 30
I'm trying things like this to no avail:
; Does not work!
(map #(prn (:a %2) %1) (iterate #(+ (:b %2) %1) 0)) my-vec)
This doesn't work because the "iterate" lazy-seq can't refer to the current element in my-vec (as far as I can tell).
TIA! Sean
user> (reduce (fn [total {:keys [a b]}]
(let [total (+ total b)]
(prn a total)
total))
0 my-vec)
"foo" 10
"bar" 23
"baz" 30
30
You could look at this as starting with a sequence of maps, filtering out a sequence of the :a values and a separate sequence of the rolling sum of the :b values and then mapping a function of two arguments onto the two derived sequences.
create sequence of just the :a and :b values with
(map :a my-vec)
(map :b my-vec)
then a function to get the rolling sum:
(defn sums [sum seq]
"produce a seq of the rolling sum"
(if (empty? seq)
sum
(lazy-seq
(cons sum
(recur (+ sum (first seq)) (rest seq))))))
then put them together:
(map #(prn %1 %s) (map :a my-vec) (sums 0 (map :b my-vec)))
This separates the problem of generating the data from processing it. Hopefully this makes life easier.
PS: whats a better way of getting the rolling sum?
Transform it into the summed sequence:
(defn f [start mapvec]
(if (empty? mapvec) '()
(let [[ m & tail ] mapvec]
(cons [(m :a)(+ start (m :b))] (f (+ start (m :b)) tail)))))
Called as:
(f 0 my-vec)
returns:
(["foo" 10] ["bar" 23] ["baz" 30])