braveandtrue

https://www.braveclojure.com/
moo 2018-09-12T00:17:26.000100Z

hi @manutter51

moo 2018-09-12T00:17:31.000100Z

I got another question

moo 2018-09-12T00:17:38.000100Z

from: ch3 ex5.

moo 2018-09-12T00:17:40.000100Z

I have this:

moo 2018-09-12T00:18:05.000100Z

(def asym-hobbit-body-parts [{:name "head" :size 3}
                             {:name "left-eye" :size 1}
                             {:name "left-ear" :size 1}
                             {:name "mouth" :size 1}
                             {:name "nose" :size 1}
                             {:name "neck" :size 2}
                             {:name "left-shoulder" :size 3}
                             {:name "left-upper-arm" :size 3}
                             {:name "chest" :size 10}
                             {:name "back" :size 10}
                             {:name "left-forearm" :size 3}
                             {:name "abdomen" :size 6}
                             {:name "left-kidney" :size 1}
                             {:name "left-hand" :size 2}
                             {:name "left-knee" :size 2}
                             {:name "left-thigh" :size 4}
                             {:name "left-lower-leg" :size 3}
                             {:name "left-achilles" :size 1}
                             {:name "left-foot" :size 2}])


; 1st attempt works, but it ugly and repetitive
(reduce (fn [acc part]
          (let [n1 {:name (clojure.string/replace (:name part) #"^left-" "c1-")
                    :size (:size part)}
                n2 {:name (clojure.string/replace (:name part) #"^left-" "c2-")
                    :size (:size part)}]
            (conj acc n1 n2))) [] asym-hobbit-body-parts)

; 2nd attempt, close but doesnt work
(require '[clojure.string :as cs])

(defn radial-seq-part [part]
  "Given a part name, provide a lazy sequence of c1-name ... c5-name strings"
  (for [c-number (range 1 6)
        :let [new-name
              {:name (cs/replace (:name part) 
                                 #"^left-" (str "c" c-number "-"))
               :size (:size part)}]
        :when (re-find #"^left-" (:name part))]
    new-name))

(map radial-seq-part asym-hobbit-body-parts) ;> does c1...c5, but omits others

; 3rd attempt

moo 2018-09-12T00:18:46.000100Z

in (defn radial-seq-part ... I’d like it to just pass the input part if that :when test fails

moo 2018-09-12T00:19:12.000100Z

currently, it returns () when the string doesn’t match #"^left-"

2018-09-12T00:20:47.000100Z

Ok, let me just catch up on what’s in the B&T text for this…

moo 2018-09-12T00:21:21.000100Z

;;;
; Problem 5
; Create a function that's similar to symmetrize-body-parts except that it has to
; work with weird space aliens with radial symmetry. Instead of two eyes, arms,
; legs, and so on, they have five.
;
; Create a function that generalizes symmetrize-body-parts and the function you
; created in Exercise 5. The new function should take a collection of body parts
; and the number of matching body parts to add. If you're completely new to Lisp
; languages and functional programming, it probably won't be obvious how to do
; this. If you get stuck, just move on to the next chapter and revisit the problem
; later.
;;;

moo 2018-09-12T00:22:26.000100Z

Thanks!

2018-09-12T00:23:31.000100Z

Ok, I skimmed kinda quickly, the goal is to take a list of body parts, and return c1..c5 for all the “left” body parts?

moo 2018-09-12T00:23:48.000200Z

yep

moo 2018-09-12T00:24:11.000100Z

and in my code there attempt 1 works, and but I’m trying to be DRY

moo 2018-09-12T00:24:18.000100Z

so I’m using for

moo 2018-09-12T00:24:32.000100Z

and it returns the expected output for #"^left-"

moo 2018-09-12T00:24:33.000200Z

but

moo 2018-09-12T00:24:43.000100Z

it returns () for non regex matching inputs

2018-09-12T00:25:31.000100Z

I think I would probably use an if so if it starts with “left-” then return the c1..c5, otherwise return a vector with just the original part

moo 2018-09-12T00:25:49.000100Z

so just if before the for

2018-09-12T00:25:53.000100Z

right

moo 2018-09-12T00:26:04.000100Z

’ight. That’s not to bad. Thanks!

2018-09-12T00:26:14.000100Z

:thumbsup:

moo 2018-09-12T00:27:46.000100Z

great it works, but it’s not flattened

moo 2018-09-12T00:28:08.000100Z

(defn radial-seq-part [part]
  "Given a part, provide a lazy sequence of c1-name
   ... c5-name strings"
  (if (re-find #"^left-" (:name part))
    (for [c-number (range 1 6)
          :let [new-name
                {:name (cs/replace (:name part)
                                   #"^left-" (str "c" c-number "-"))
                 :size (:size part)}]
          :when (re-find #"^left-" (:name part))]
      new-name)
    part))

(map radial-seq-part asym-hobbit-body-parts) 

2018-09-12T00:28:23.000200Z

use mapcat instead of map

moo 2018-09-12T00:28:28.000100Z

--->
({:name "head", :size 3}
 ({:name "c1-eye", :size 1}
  {:name "c2-eye", :size 1}
  {:name "c3-eye", :size 1}
  {:name "c4-eye", :size 1}
  {:name "c5-eye", :size 1})
 ({:name "c1-ear", :size 1}
  {:name "c2-ear", :size 1}
  {:name "c3-ear", :size 1}
  {:name "c4-ear", :size 1}
  {:name "c5-ear", :size 1})
 {:name "mouth", :size 1}
 {:name "nose", :size 1}
 {:name "neck", :size 2}
 ({:name "c1-shoulder", :size 3} ; and so on

moo 2018-09-12T00:28:36.000100Z

I’ll check that out. ty!

moo 2018-09-12T00:50:31.000100Z

mapcat flattens but the passed through parts from if are vectors not maps

moo 2018-09-12T00:50:47.000100Z

([:name "head"]
 [:size 3]
 {:name "c1-eye", :size 1}
 {:name "c2-eye", :size 1}
 {:name "c3-eye", :size 1}; and so on...

moo 2018-09-12T00:51:23.000100Z

I could -> flatten map

2018-09-12T00:54:17.000100Z

Try wrapping the mapcat with (into {} ...)

2018-09-12T00:54:44.000100Z

although hmm, not quite

moo 2018-09-12T00:55:03.000100Z

(flatten (map … ) works

2018-09-12T00:55:50.000100Z

Does it? I thought flatten would give you something like (:name "head" :size 3 :name "c1-eye" :size 1...)

2018-09-12T00:56:17.000100Z

Oh well, I rarely use flatten, it’s probably just my ignorance

moo 2018-09-12T00:56:37.000100Z

(def c1to5-body-parts
  (-> (map radial-seq-part asym-hobbit-body-parts)
      flatten))

; ==>
({:name "head", :size 3}
 {:name "c1-eye", :size 1}
 {:name "c2-eye", :size 1}
 {:name "c3-eye", :size 1}
 {:name "c4-eye", :size 1}
 {:name "c5-eye", :size 1}
 {:name "c1-ear", :size 1}
 {:name "c2-ear", :size 1}
 {:name "c3-ear", :size 1}
 {:name "c4-ear", :size 1}
 {:name "c5-ear", :size 1} and so on

2018-09-12T00:57:31.000100Z

awesome

moo 2018-09-12T00:58:24.000100Z

so my sol’n is

(require '[clojure.string :as cs])

(defn radial-seq-part [part]
  "Given a part, provide a lazy sequence of c1-name
   ... c5-name strings"
  (if (re-find #"^left-" (:name part)) ; if it's a left- string
    (for [c-number (range 1 6) ; then return a lazy seqence
          :let [new-name
                {:name (cs/replace (:name part)
                                   #"^left-" (str "c" c-number "-"))
                 :size (:size part)}]
          :when (re-find #"^left-" (:name part))]
      new-name)
    part)) ; otherwise return the original part

(def c1to5-body-parts
  (-> (map radial-seq-part asym-hobbit-body-parts)
      flatten)) 

moo 2018-09-12T00:58:29.000100Z

not to ugly. Thanks!

moo 2018-09-12T00:59:49.000100Z

it’s the for that’s adding the extra nesting

moo 2018-09-12T01:00:01.000100Z

because that’s a sequence of maps

moo 2018-09-12T01:00:07.000100Z

so… it makes sense

moo 2018-09-12T01:00:13.000100Z

that I need to flatten

moo 2018-09-12T01:00:34.000100Z

I suppose :when is redundant

2018-09-12T01:01:19.000100Z

There’s one other thing you might try: on the line that says “otherwise return the original part”, instead of returning part return (list part). Then the unchanged parts will be inside a list just like the radial parts. Then go back to mapcat instead of map/flatten

2018-09-12T01:01:48.000100Z

(and yeah, you don’t need the :when now)

moo 2018-09-12T01:02:09.000100Z

tried it,… still nested

2018-09-12T01:02:57.000100Z

what’s it look like?

moo 2018-09-12T01:03:06.000100Z

oh nm, you’re right

moo 2018-09-12T01:03:22.000100Z

(defn radial-seq-part [part]
  "Given a part, provide a lazy sequence of c1-name
   ... c5-name strings"
  (if (re-find #"^left-" (:name part)) ; if it's a left- string
    (for [c-number (range 1 6) ; then return a lazy seqence
          :let [new-name
                {:name (cs/replace (:name part)
                                   #"^left-" (str "c" c-number "-"))
                 :size (:size part)}]
          :when (re-find #"^left-" (:name part))] ;:when is redundant inside if
      new-name)
    (list part))) ; otherwise return the original part

(def c1to5-body-parts
  (mapcat radial-seq-part asym-hobbit-body-parts))

moo 2018-09-12T01:03:34.000100Z

what happened there with the list?

2018-09-12T01:04:38.000100Z

So the idea is that for returns a list and part isn’t a list. So just wrap it in its own list, and voila, everything is a list

😁 1
moo 2018-09-12T01:05:06.000100Z

gotcha

moo 2018-09-12T01:05:17.000100Z

so mapcat has consistent types

moo 2018-09-12T01:05:36.000100Z

thanks again man!

moo 2018-09-12T01:05:56.000100Z

I finished the book, and now I’m doing each chapters exercises…

2018-09-12T01:06:08.000100Z

cool

moo 2018-09-12T01:06:52.000100Z

alright, that’s all for my day. See ya!

2018-09-12T01:18:31.000100Z

have a good one

moo 2018-09-12T13:16:58.000100Z

Can you help me understand what’s happening here? https://www.braveclojure.com/core-functions-in-depth/#A_Vampire_Data_Analysis_Program_for_the_FWPD

(defn mapify
  "Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
  [rows]
  (map (fn [unmapped-row]
         (reduce (fn [row-map [vamp-key value]]
                   (assoc row-map vamp-key (convert vamp-key value)))
                 {}
                 (map vector vamp-keys unmapped-row)))
       rows))

moo 2018-09-12T13:17:48.000100Z

(defn mapify
  "Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
  [rows]
  (map (fn [unmapped-row] ; this anonymous fn is mapped on all rows
         (reduce (fn [row-map [vamp-key value]] ; ???
                   (assoc row-map vamp-key (convert vamp-key value)))
                 {}
                 (map vector vamp-keys unmapped-row)))
       rows))

moo 2018-09-12T13:19:50.000100Z

I’m working on ch. 4, ex. 3. where I’m supposed to create a similar set up for validation. However I don’t get this mapify This is what I have so far:

(def vamp-validators
  {:name #(not-empty %)
   :glitter-index #(> % 0)})

(defn validate-entry
 [vamp-key value]
 ((get vamp-validators vamp-key) value))

2018-09-12T13:20:25.000100Z

What kind of data is mapify being called on in the original example?

moo 2018-09-12T13:20:40.000100Z

A CSV

moo 2018-09-12T13:20:59.000100Z

and convert has keys that map to functions to parse string to int etc.

moo 2018-09-12T13:21:14.000100Z

;;; Set up
; use absolute path to get around any issues with working directory.
(def filename "/Users/moo/Sync/braveandtrue/by-chapter/ch04-suspects.csv")

(def vamp-keys [:name :glitter-index])

(defn str->int
  [str]
  (Integer. str))

(def conversions {:name identity :glitter-index str->int})

(defn convert
  [vamp-key value]
  ((get conversions vamp-key) value))

(defn parse
  "Convert a CSV file into rows of columns"
  [string]
  (map #(clojure.string/split % #",")
       (clojure.string/split string #"\n")))

(defn mapify
  "Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
  [rows]
  (map (fn [unmapped-row]
         (reduce (fn [row-map [vamp-key value]]
                   (assoc row-map vamp-key (convert vamp-key value)))
                 {}
                 (map vector vamp-keys unmapped-row)))
       rows))

(defn glitter-filter
  [minimum-glitter records]
  (filter #(>= (:glitter-index %) minimum-glitter) records))

(parse (slurp filename))

(def glitter-map
  (-> (slurp filename)
      parse
      mapify))

2018-09-12T13:22:16.000100Z

Hmm, I think I’m following.

moo 2018-09-12T13:23:02.000100Z

I’m trying to write vamp-validators, validate-entry and validate. The latter of which is like mapify

moo 2018-09-12T13:23:19.000100Z

but I don’t get mapify

2018-09-12T13:24:14.000200Z

So for simplicity, let’s assume that vamp-keys is [:a :b :c :d] and we have a row that’s [1 2 3 4]. Calling map with multiple collections just takes an entry from each collection and passes the entries to whatever function you’re mapping with, in this case vector

2018-09-12T13:25:27.000100Z

So in our example, (map vector vamp-keys unmapped-row) is going to return [[:a 1] [:b 2] [:c 3] [:d 4]]

moo 2018-09-12T13:27:13.000100Z

gotcha so (map vector vamp-keys unmapped-row) is taking

[:name :glitter-index] and ["bob" "10"] and returning {:name "bob" :glitter-index "10"}

2018-09-12T13:27:28.000100Z

Not quite

2018-09-12T13:27:42.000100Z

You have curly braces, I have square braces

moo 2018-09-12T13:28:19.000100Z

[:name :glitter-index] and ["bob" "10"] and returning [:name "bob" :glitter-index "10"]

moo 2018-09-12T13:28:31.000100Z

because this is input to vector

2018-09-12T13:29:00.000100Z

Right. You’re getting :key value, which looks like it should be in a map, but in this case we’re mapping vector over the entries, so we are just getting back a vector with a pair of entries

moo 2018-09-12T13:29:27.000100Z

k, and this becomes input to reduce

2018-09-12T13:30:06.000100Z

Right, each of those 2-element vectors is passed in as the 2nd arg to your reducing function

2018-09-12T13:30:17.000100Z

(The first arg is the map you’re accumulating)

2018-09-12T13:30:55.000100Z

Actually, I missed a slight mistake in your value, above

2018-09-12T13:31:35.000100Z

The map function isn’t going to return [:name "bob" :glitter-index "10"], it’s going to give you a list of 2-element vectors

2018-09-12T13:31:58.000200Z

i.e. '([:name "bob"] [:glitter-index "10"])

moo 2018-09-12T13:33:05.000100Z

(defn mapify
  "Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
  [rows]
  (map (fn [unmapped-row]
         ; reduce an anonymous function over our list of two element vectors
         ; ([:name "Bob"] [:glitter-index "10"])
         (reduce (fn [row-map [vamp-key value]]
                   (assoc row-map vamp-key (convert vamp-key value)))
                 {} ; reduce starts with an empty map
                 ; combine [:name :glitter-index] with input csv row data e.g. ["Bob" "10"]
                 ; to create ([:name "Bob"] [:glitter-index "10"])
                 (map vector vamp-keys unmapped-row)))
       rows))

2018-09-12T13:33:54.000100Z

That looks good — as a purist, I’d say to create '([:name "Bob"] [:glitter-index "10"])

moo 2018-09-12T13:34:57.000100Z

so I’m following along … until reduce

2018-09-12T13:36:02.000100Z

without the single quote, it implies that you’re trying to call [:name "Bob"] as a function and passing [:glitter-index "10"] as an argument.

moo 2018-09-12T13:36:24.000100Z

I see

2018-09-12T13:36:36.000100Z

It’s in a comment so it won’t break anything, but probably a good idea to get in the habit

2018-09-12T13:36:52.000100Z

Anyway, the reduce

2018-09-12T13:37:50.000100Z

You have {} as the initial value for what you’re trying to accumulate, and you’ve got a list like '([:name "Bob"] [:glitter-index "10"] ...) to iterate over

2018-09-12T13:38:42.000100Z

The first time thru, it’s going to call the reducing function with {} as the first argument, and [:name "Bob"] as the 2nd argument.

moo 2018-09-12T13:39:35.000100Z

oh, so that’s why row-map is our first argument in the anon fn

2018-09-12T13:39:36.000100Z

Since you’ve got destructuring going on, that’s going to assign :name to vamp-key and "Bob" to value

2018-09-12T13:40:00.000100Z

right, row-map will be {} the first time thru

moo 2018-09-12T13:40:57.000200Z

somehow I don’t see how it turns into a legit map

moo 2018-09-12T13:41:03.000100Z

it seems like this ’ought to happen:

moo 2018-09-12T13:41:26.000200Z

{:name “Bob”} {:glitter-index 10}

2018-09-12T13:42:05.000100Z

I assume convert is just looking up the key to do things like “Ok, :name should be a string, just return it, but :glitter-index should be a number, so parse the string and return the actual number” etc.

moo 2018-09-12T13:42:21.000100Z

(def conversions {:name identity :glitter-index str->int})

(defn convert
  [vamp-key value]
  ((get conversions vamp-key) value))

moo 2018-09-12T13:42:38.000100Z

it’s just type casting the number

2018-09-12T13:42:39.000100Z

Ok, cool, that’s what I expected

moo 2018-09-12T13:42:42.000100Z

based on the key

2018-09-12T13:43:30.000100Z

So now inside the function, you’re calling (assoc row-map :name "Bob") (once convert is done converting it)

2018-09-12T13:43:46.000100Z

and that’s going to return {:name "Bob"}

moo 2018-09-12T13:43:53.000100Z

I’m with ya

2018-09-12T13:44:21.000100Z

But this is reduce, not map, so that means the next time through, the first argument is going to be the result of the last time thru.

2018-09-12T13:45:00.000100Z

In other words, the 2nd time, we’re going to call the function with the arguments {:name "Bob"} and [:glitter-index "10"]

moo 2018-09-12T13:45:21.000100Z

then, I’m imagining {:name “bob” :gl 2 :name “otherguy” :gl 3} which diesnt make sense

2018-09-12T13:45:51.000100Z

Yeah, it’s a little tricky, but that’s not what’s happening

moo 2018-09-12T13:46:05.000100Z

what happens the 3rd time?

2018-09-12T13:46:22.000100Z

For each row, you only have each key once

2018-09-12T13:46:47.000100Z

:name "otherguy" is the next row

2018-09-12T13:47:28.000100Z

so that’s not part of the list that gets passed to the reduce — it’s a reduce nested inside a map

moo 2018-09-12T13:48:06.000100Z

oh!

moo 2018-09-12T13:48:13.000200Z

we’re just reducing two entries

moo 2018-09-12T13:48:23.000100Z

then it’s back to getting mapped onto a new row

2018-09-12T13:48:43.000100Z

Well, if there’s only 2 entries per row, yes

moo 2018-09-12T13:48:58.000200Z

reduce is operating on only one row

2018-09-12T13:49:07.000100Z

Right

moo 2018-09-12T13:49:14.000100Z

and map is applying reduce row-by-row

moo 2018-09-12T13:49:22.000100Z

a ha

moo 2018-09-12T13:49:24.000100Z

great!

2018-09-12T13:50:18.000100Z

It would be easier to follow if you pulled the inner reduce out into its own function called convert-row-to-map or something

2018-09-12T13:51:06.000100Z

Then mapify would just be (defn mapify [rows] (map convert-row-to-map rows))

moo 2018-09-12T13:51:25.000100Z

yes

moo 2018-09-12T13:51:27.000100Z

true

2018-09-12T13:52:30.000100Z

Incidently, that’s a coding style I call “executable documentation” — when you read mapify, you know exactly what it’s doing because it says it’s converting rows to maps.

2018-09-12T13:53:03.000200Z

So sometimes it’s worth pulling out the inner stuff into a named function, because the name of the fn is good documentation for what it is you’re trying to do.

moo 2018-09-12T13:54:18.000100Z

that makes sense. 🙂

moo 2018-09-12T13:54:44.000100Z

so, I can imagine a few ways to make validators, but they don’t seem very slick

moo 2018-09-12T13:54:48.000100Z

for example

moo 2018-09-12T13:55:31.000100Z

I could just do ifs for name and glitter-index for an entry, then use map to apply it

moo 2018-09-12T13:55:46.000100Z

however, I want this to return false right away

moo 2018-09-12T13:56:22.000100Z

anyways, I ’ll go puzzle it out 🙂

moo 2018-09-12T13:56:31.000100Z

thanks again man, that really helped!

2018-09-12T13:56:46.000100Z

:thumbsup:

moo 2018-09-12T14:05:28.000100Z

is there an non macro version of and

moo 2018-09-12T14:05:45.000100Z

trying to (apply and (false true false true))

moo 2018-09-12T14:06:10.000100Z

if I use map with validation, I’ll get a lazy seq of booleans

moo 2018-09-12T14:08:55.000100Z

I guess I can use some

2018-09-12T14:09:34.000100Z

there’s also every?

2018-09-12T14:10:23.000100Z

That’s probably what you want instead of apply and

moo 2018-09-12T14:31:26.000100Z

great

moo 2018-09-12T14:31:30.000100Z

this is what I came up with

moo 2018-09-12T14:31:35.000100Z

(def vamp-validators
  {:name #(if (not-empty %) true false)
   :glitter-index #(>= % 0)})

(defn validate-element
  [vamp-key value]
  ((get vamp-validators vamp-key) value))

(defn validate-entry [entry]
  (not (some #(= false %)
      (for [foundkey (keys entry)]
        (validate-element foundkey (foundkey entry))))))
    ;    ((get vamp-validators foundkey) (foundkey entry))))))

(defn validate [entries]
  (not #(some #(= false %) (map validate-entry entries))))

moo 2018-09-12T14:35:49.000100Z

cool, every is what I needed

moo 2018-09-12T14:37:13.000100Z

(def vamp-validators
  {:name #(if (not-empty %) true false)
   :glitter-index #(>= % 0)})

(defn validate-element
  [vamp-key value]
  ((get vamp-validators vamp-key) value))

(defn validate-entry [entry]
  (every? true?
          (for [foundkey (keys entry)]
            (validate-element foundkey (foundkey entry)))))

(defn validate [entries]
  (every? true? (map validate-entry entries)))

2018-09-12T14:40:40.000100Z

Looks good — there’s some optimizations you can apply

2018-09-12T14:41:45.000100Z

You can replace (not-empty some-string) with (seq some-string)

2018-09-12T14:41:56.000100Z

(seq "a")
=> (\a)
(seq "")
=> nil

2018-09-12T14:44:56.000100Z

You can also replace every? true? just every? if you re-work your code a bit — there’s an implicit map inside every?, so you can do (every? validate-entry entries) for example

2018-09-12T14:45:39.000100Z

You can do the same sort of thing to get rid of the for, but it’s a bit tricker. Maybe you’d like to work that one out for yourself. 🙂