Skip to content

Instantly share code, notes, and snippets.

@apbendi
Last active October 22, 2021 02:16
Show Gist options
  • Save apbendi/f9a2c2ea80167cdc005b to your computer and use it in GitHub Desktop.
Save apbendi/f9a2c2ea80167cdc005b to your computer and use it in GitHub Desktop.
Solutions: Clojure for the Brave and True (braveclojure.com)
; In Clojure for the Brave and True (braveclojuure.com),
; at the end of the chapter "Core Functions in Depth", the author
; prevents several challanges to build on top of some sample code
; he has provided
; This code is provided by the author:
;; In ns below, notice that "gen-class" was removed
(ns fwpd.core
;; We haven't gone over require but we will.
(:require [clojure.string :as s]))
(def filename "suspects.csv")
;; Later on we're going to be converting each row in the CSV into a
;; map, like {:name "Edward Cullen" :glitter-index 10}.
;; Since CSV can't store Clojure keywords, we need to associate the
;; textual header from the CSV with the correct keyword.
(def headers->keywords {"Name" :name
"Glitter Index" :glitter-index})
(defn str->int
"If argument is a string, convert it to an integer"
[str]
(if (string? str)
(read-string (re-find #"^-?\d+$" str))
str))
;; CSV is all text, but we're storing numeric data. We want to convert
;; it back to actual numbers.
(def conversions {:name identity
:glitter-index str->int})
(defn parse
"Convert a csv into rows of columns"
[string]
(map #(s/split % #",")
(s/split string #"\n")))
(defn mapify
"Return a seq of maps like {:name \"Edward Cullen\" :glitter-index 10}"
[rows]
(let [;; headers becomes the seq (:name :glitter-index)
headers (map #(get headers->keywords %) (first rows))
;; unmapped-rows becomes the seq
;; (["Edward Cullen" "10"] ["Bella Swan" "0"] ...)
unmapped-rows (rest rows)]
;; Now let's return a seq of {:name "X" :glitter-index 10}
(map (fn [unmapped-row]
;; We're going to use map to associate each header with its
;; column. Since map returns a seq, we use "into" to convert
;; it into a map.
(into {}
;; notice we're passing multiple collections to map
(map (fn [header column]
;; associate the header with the converted column
[header ((get conversions header) column)])
headers
unmapped-row)))
unmapped-rows)))
(defn glitter-filter
[minimum-glitter records]
(filter #(>= (:glitter-index %) minimum-glitter) records))
; Here are possible solutions to the challenges:
(defn validate-record [new-suspect]
(and (:name new-suspect) (:glitter-index new-suspect)))
(defn prepend-suspect [new-suspect suspect-list]
(if (validate-record new-suspect)
(conj suspect-list new-suspect)
false)
)
(def keywords->headers {:name "Name"
:glitter-index "Glitter Index"})
(defn demapify [suspect-list]
(let [header-keys (keys (first suspect-list))
headers (map #(get keywords->headers %) header-keys)
header-row (clojure.string/join "," headers)
]
(str header-row "\n" (clojure.string/join "\n"
(map #(clojure.string/join "," %)
(map (fn [suspect]
(map #(get suspect %) header-keys))
suspect-list))))
)
)
; In Clojure for the Brave and True (braveclojure.com),
; chapter "Functional Programming", Secion 3 "Cool Things to do With Pure Functions"
; the author discusses Clojure's comp function and shows an re-implementation which
; can compose two functions:
(defn two-comp
[f g]
(fn [& args]
(f (apply g args))))
; The author then encourages the reader to try reimplementing the comp function
; with unlimited arguments: "Also, try re-implementing Clojure's comp so that
; you can compose any number of functions."
; Here are two possible solutions:
; In this solution, a let block is used to assign new symbols
; purely for readability's sake, and then recur is called which
; recalls the method with the new arguments. The first argument
; is the composition of all functions processed so far, the second
; is the remaining functions yet to be composed.
(defn new-comp
[f & gs]
(if (empty? gs)
f
(let
[g (first gs)
new-gs (rest gs)]
(recur (fn [& args]
(f (apply g args))) new-gs)
))
; The second solution uses a loop to do the recursion
; The vector in the loop call sets up the initial values of the symbols
; in the same format as let. I.e. [symbol value symbol value]
; The call to recur then updates the value of f and g,
; essentially ignoring the fun-one and rest-funs symbols.
;This behavior was not initially obvious to me as a Clojure noob.
(defn my-comp
[fun-one & rest-funs]
(loop [f fun-one gs rest-funs]
(if (empty? gs)
f
(recur (fn [& args]
(f (apply (first gs) args)))
(rest gs))
)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment