0

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:

  1. create here function chop-chop with parameter coll

  2. then we apply function partition-by for coll, but I’m not sure what identity is

  3. next we bound result of previous operation to x

  4. 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!

5
  • 2
    If you notice words like "identity" that you don't know, then this page will help a lot: clojure.github.io/clojure/index.html is the Clojure function index. Commented Feb 7, 2021 at 10:34
  • 1
    @BipedPhill do you prefer that to ClojureDocs? clojuredocs.org/clojure.core/identity Commented Feb 7, 2021 at 11:25
  • 3
    @AaronBell I like to make sure people who have not yet found "the manual" learn where to find the real thing (clojure.org and clojure.github.io). There's room for two tabs in every browser :-) Commented Feb 7, 2021 at 15:19
  • 2
    (for [x (partition-by identity "aaaabbbcca")] [(first x) (count x)]) Commented Feb 9, 2021 at 13:48
  • 2
    (map (juxt first count) (partition-by identity "aaaabbbcca")) Commented Feb 9, 2021 at 13:50

2 Answers 2

5

The jist

This example:

  1. Takes a collection (in this case, a string)
  2. Gets the groups of repeating letters
  3. Maps over the groups to get the representative letter
  4. Maps over the groups to get the count of each group
  5. 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:

  1. Yup, it's a single-arity function defined with the defn macro
  2. 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))
  1. 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))
  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!

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for such clear and detailed explanation! I'll try my best to learn Clojure!
4

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))

1 Comment

Thank you very much for clear explanation!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.