Last active
September 13, 2018 19:38
-
-
Save albert-yu/e4666ed5424ad2ac97e316b6b679bc2d to your computer and use it in GitHub Desktop.
Clojure from the ground up
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;----------------------------------------------------------------- | |
;; 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)) | |
))) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;-------------------------------------------------------------- | |
;; 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;------------------------------------------------------------- | |
;; 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