Skip to content

Instantly share code, notes, and snippets.

@albert-yu
Last active September 13, 2018 19:38
Show Gist options
  • Save albert-yu/e4666ed5424ad2ac97e316b6b679bc2d to your computer and use it in GitHub Desktop.
Save albert-yu/e4666ed5424ad2ac97e316b6b679bc2d to your computer and use it in GitHub Desktop.
Clojure from the ground up
;;-----------------------------------------------------------------
;; SEQUENCES
;; https://aphyr.com/posts/304-clojure-from-the-ground-up-sequences
;;-----------------------------------------------------------------
;; [] vector
;; #{} hashset?
; Problems
; Write a function to find out if a string is a palindrome–that is, if it looks the same forwards and backwards.
(defn is-palindrome [s]
(= s (apply str (reverse s))))
; Find the number of ‘c’s in “abracadabra”.
(defn num-chars [s c]
(count
(filter
(fn [el] (= c el)) (seq s))))
; Write your own version of filter.
(defn my-filter [f coll]
(remove (complement f) coll))
;; returns list
(defn filter-2 [f coll]
(reduce (fn [output elem]
(if (f elem)
(conj output elem)
output
))
[]
coll))
;; returns vector (why?)
; Find the first 100 prime numbers: 2, 3, 5, 7, 11, 13, 17, ….
(defn first-n-primes [n]
"Returns the first n primes"
(take n
(reduce
(fn [primesthusfar num]
(if (some zero? (map (fn [x] (mod num x)) primesthusfar))
primesthusfar
(conj primesthusfar num)))
[2]
(take 1000 (iterate inc 3))
)))
;;--------------------------------------------------------------
;; MACROS
;; https://aphyr.com/posts/305-clojure-from-the-ground-up-macros
;;--------------------------------------------------------------
; Using the control flow constructs we’ve learned,
; write a schedule function which, given an hour of
; the day, returns what you’ll be doing at that time.
; (schedule 18), for me, returns :dinner.
(defn schedule [hour]
(cond
(or (< hour 0) (>= hour 24)) :na
(< hour 5) :sleep
(and (<= hour 6) (>= hour 5)) :gym
(<= hour 8) :breakfast
(<= hour 12) :work
(<= hour 13) :lunch
(<= hour 17) :work
(<= hour 19) :dinner
(<= hour 21) :study
:else :sleep))
; Using the threading macros, find how many numbers
; from 0 to 9999 are palindromes: identical when
; written forwards and backwards. 121 is a palindrome, as
; is 7447 and 5, but not 12 or 953.
(defn is-palindrome? [num]
(= (str num) (apply str (reverse (str num)))))
(defn find-palindromes []
(->>
(range 10000)
(filter is-palindrome?)))
; Write a macro id which takes a function and a list of
; args: (id f a b c), and returns an expression which calls
; that function with the given args: (f a b c).
(defmacro id [f & args]
(cons f args))
;; e.g.
;; (id str "la " "la " "land")
;; => "la la land"
; Write a macro log which uses a var, logging-enabled,
; to determine whether or not to print an expression to the
; console at compile time. If logging-enabled is false,
; (log :hi) should macroexpand to nil. If logging-enabled is
; true, (log :hi) should macroexpand to (prn :hi). Why would
; you want to do this check during compilation, instead of when
; running the program? What might you lose?
;; setup
(def temp-bool true)
(def logging-enabled (var temp-bool))
;; macro definition
(defmacro log [msg]
(if (var? logging-enabled)
(if (deref logging-enabled)
`(prn ~msg)
nil)
nil))
;; (macroexpand '(log :hi))
;; => (clojure.core/prn :hi)
;; (alter-var-root logging-enabled not)
;; => false
;; (macroexpand '(log :hi))
;; => nil
;; The advantage of doing this during compilation is that
;; the code itself ignores the print message. There is no
;; need to check if logging-enabled is set at run-time before
;; each print statement, thus reducing overhead.
;; The disadvantage is that if you want to dynamically toggle
;; logging, you will have to re-compile.
; (Advanced) Using the rationalize function, write a macro
; exact which rewrites any use of +, -, *, or / to force
; the use of ratios instead of floating-point numbers.
; (* 2452.45 100) returns 245244.99999999997, but
; (exact (* 2452.45 100)) should return 245245N
(defmacro exact [input-list]
(let [sym (first input-list)]
`(~sym ~@(map rationalize (rest input-list)))))
;; (macroexpand '(exact (* 2452.45 100)))
;; => (* 49049/20 100)
;; (exact (* 2452.45 100))
;; => 245245N
;;-------------------------------------------------------------
;; STATE
;; https://aphyr.com/posts/306-clojure-from-the-ground-up-state
;;-------------------------------------------------------------
; Finding the sum of the first 10000000 numbers takes about 1 second on my machine:
; user=> (defn sum [start end] (reduce + (range start end)))
; user=> (time (sum 0 1e7))
; "Elapsed time: 1001.295323 msecs"
; 49999995000000
; Use delay to compute this sum lazily; show that it takes no
; time to return the delay, but roughly 1 second to deref.
(do
(defn sum [start end]
(reduce + (range start end)))
(time (def lazy-sum (delay (sum 0 1e7))))
(time (deref lazy-sum)))
;; => "Elapsed time: 0.174198 msecs"
;; => "Elapsed time: 247.136784 msecs"
;; => 49999995000000
; We can do the computation in a new thread directly,
; using (.start (Thread. (fn [] (sum 0 1e7)))–but this simply
; runs the (sum) function and discards the results. Use a promise
; to hand the result back out of the thread. Use this technique to
; write your own version of the future macro.
(def x
(let [summation (promise)]
(.start
(Thread. (fn []
(deliver summation (sum 0 1e7)))))
summation))
;; @x
;; => 49999995000000
(defmacro my-future
"Looks bleak"
[& body]
(let [result (promise)
expr (conj body :do)]
(.start
(Thread. (fn []
(deliver result (eval expr)))))
result))
(def x (my-future (prn "hi") (+ 1 2)))
;; => #'user/x
;; => "hi"
;; @x
;; => 3
; If your computer has two cores, you can do this expensive
; computation twice as fast by splitting it into two parts:
; (sum 0 (/ 1e7 2)), and (sum (/ 1e7 2) 1e7), then adding
; those parts together. Use future to do both parts at once,
; and show that this strategy gets the same answer as the
; single-threaded version, but takes roughly half the time.
(defn fast-sum []
(let [left (future (sum 0 (/ 1e7 2)))
right (future (sum (/ 1e7 2) 1e7))]
(+ @left (rationalize @right))))
;; (time (fast-sum))
;; => "Elapsed time: 107.686457 msecs"
;; => 49999995000000N
;; (time (sum 0 1e7))
;; => "Elapsed time: 218.225652 msecs"
;; => 49999995000000
; Instead of using reduce, store the sum in an atom and use
; two futures to add each number from the lower and upper
; range to that atom. Wait for both futures to complete
; using deref, then check that the atom contains the right
; number. Is this technique faster or slower than reduce?
; Why do you think that might be?
(defn add-to
"Adds a number to atom, an atom"
[atom num]
(swap! atom + num))
(defn sum-range
"Adds a list of numbers to an atom"
[atom nums]
(doseq [item nums] (add-to atom item))
atom)
(defn atomic-sum [start end]
(let [result (atom 0)]
(let [left (future (sum-range result (range start (/ end 2))))
right (future (sum-range result (range (/ end 2) end)))]
(deref left)
(deref right))
@result))
;; (time (atomic-sum 0 1e7))
;; => "Elapsed time: 735.251277 msecs"
;; => 4.9999995E13
;;
;; This is expected, since both threads are trying to access
;; the same piece of memory concurrently, causing locks.
; Instead of using a lazy list, imagine two threads are removing
; tasks from a pile of work. Our work pile will be the list of
; all integers from 0 to 10000:
; user=> (def work (ref (apply list (range 1e5))))
; user=> (take 10 @work)
; (0 1 2 3 4 5 6 7 8 9)
;
; And the sum will be a ref as well:
; user=> (def sum (ref 0))
;
; Write a function which, in a dosync transaction, removes the
; first number in work and adds it to sum.
; Then, in two futures, call that function over and over again
; until there’s no work left. Verify that @sum is 4999950000.
; Experiment with different combinations of alter and commute–
; if both are correct, is one faster? Does using deref instead
; of ensure change the result?
(def work (ref (apply list (range 1e5))))
(def sum (ref 0))
(defn do-work
"Takes the first item from work and adds it to sum"
[]
(dosync
; (prn (count @work))
(when (> (count @work) 0)
(alter sum + (first (deref work)))
;; rm first element
(alter work pop))))
(defn threadmethod []
(while (not (empty? @work))
(do-work)))
(defn execute
[]
(let [thread1 (future (threadmethod))
thread2 (future (threadmethod))]
(deref thread1)
(deref thread2)
@sum))
;; (execute)
;; => 4999950000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment