├── .gitignore ├── LICENSE ├── README.md ├── emacs.md ├── exercises ├── project.clj ├── resources │ └── ex08-movie-time │ │ └── ratings.list └── src │ └── training │ ├── core.clj │ ├── exercises │ ├── ch01_find_the_gap.clj │ ├── ex01_total_word_count.clj │ ├── ex02_using_data_types.clj │ ├── ex03_banned_words.clj │ ├── ex04_function_basics.clj │ ├── ex05_odds.clj │ ├── ex06_more_syntax.clj │ ├── ex07_core_functions.clj │ ├── ex08_movie_time.clj │ ├── ex09_review_core.clj │ ├── ex10_functional_programming.clj │ ├── ex10_functional_programming.js │ ├── ex11_read_eval_macros.clj │ ├── ex12_java_interop.clj │ ├── ex13_multimethods.clj │ ├── ex14_protocols_records.clj │ └── ex15_schema.clj │ └── solutions │ ├── s00_word_count.clj │ ├── s01_total_word_count.clj │ ├── s03_banned_words.clj │ ├── s04_function_basics.clj │ ├── s05_odds.clj │ ├── s07_core_functions.clj │ ├── s08_movie_time.clj │ ├── s12_java_interop.clj │ └── sch01_find_the_gap.clj ├── projects ├── blackjack │ ├── .gitignore │ ├── README.md │ ├── project.clj │ └── src │ │ └── blackjack │ │ └── core.clj ├── character-creator │ ├── .gitignore │ ├── README.md │ ├── project.clj │ └── src │ │ └── character_creator │ │ └── core.clj ├── hn │ ├── .gitignore │ ├── README.md │ ├── project.clj │ └── src │ │ └── hn │ │ └── core.clj ├── mic-check │ ├── .gitignore │ ├── README.md │ ├── project.clj │ └── src │ │ └── mic_check │ │ └── core.clj ├── phrasebook │ ├── PirateConversation.java │ ├── PiratePhrases.java │ └── pirate_phrases │ │ ├── Farewells.java │ │ └── Greetings.java └── valeria │ ├── .gitignore │ ├── README.md │ ├── project.clj │ ├── resources │ ├── citizens.edn │ └── monsters.edn │ └── src │ └── valeria │ ├── cards.clj │ ├── core.clj │ └── odds.clj └── real ├── parse ├── .gitignore ├── README.md ├── project.clj ├── resources │ ├── invalid.csv │ └── valid.csv └── src │ └── parse │ └── core.clj └── transform ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc └── intro.md ├── project.clj ├── src └── transform │ └── core.clj └── test └── transform └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | .lein-* 9 | .nrepl-port 10 | repl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is licensed under the [Creative Commons Attribution-NonCommercial 4.0 license.](https://creativecommons.org/licenses/by-nc/4.0/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intro to Clojure Training Materials 2 | 3 | [**Learn more about Brave Clojure On-Site Training**](http://www.braveclojure.com/training/) 4 | 5 | Hello! 6 | 7 | This repo contains all the examples, exercises, and projects we'll be 8 | working on. Every exercise and project you'll be asked to do has an 9 | example solution or implementation. You can look at these before or 10 | during the workshop, or not - whatever helps you learn better. 11 | 12 | The [`exercises`](exercises) directory includes a Leiningen project 13 | that has all the code and exercises we'll be looking at during the 14 | lectures, as well as solutions under 15 | [`exercises/src/training/solutions`](exercises/src/training/solutions). I 16 | recommend opening the `exercises` directory in your editor, connecting 17 | to a REPL, and trying the examples and exercises interactively. 18 | 19 | The [`projects`](projects) directory contains example implementations 20 | for the larger projects you'll be working on. For self-study, first 21 | use `lein run` to see how the program is supposed to behave. Then try 22 | deleting individual functions and reimplementing them from scratch, or 23 | try reimplementing the entire project. 24 | 25 | Have fun! 26 | 27 | ## Extra Exercises 28 | 29 | If you finish exercises early, check out these sites for additional practice: 30 | 31 | * http://exercism.io/ 32 | * https://projecteuler.net/ 33 | * http://4clojure.com/ 34 | 35 | ## Keep Learning 36 | 37 | One of my goals for this workshop is to provide you with a sturdy 38 | foundation so that you can quickly and easily continue learning on 39 | your own. Here are my recommendations for books, tutorials, and 40 | projects, and other resources: 41 | 42 | * [Land of Lisp](http://www.amazon.com/Land-Lisp-Learn-Program-Game/dp/1593272812). This 43 | is the book that got me started with Lisp. It covers Common Lisp, 44 | which isn't as immediately practical, but it still helps you 45 | understand Clojure better. Plus, learning Common Lisp is extremely 46 | useful because you'll be able to understand other great resources, 47 | like.. 48 | * [On Lisp](http://www.paulgraham.com/onlisp.html). This is one of the 49 | best books on learning how to think like a Lisp programmer. 50 | * [Clojure Applied](http://www.amazon.com/Clojure-Applied-Practitioner-Ben-Vandgrift/dp/1680500740/) 51 | is billed as a second book for Clojure, and I think it does a good job. 52 | * [The Clojure subreddit](https://www.reddit.com/r/clojure) is very 53 | friendly and helpful, as is the [Clojure google group](https://groups.google.com/forum/#!forum/clojure). 54 | 55 | For projects, my advice is to think of something that you personally 56 | want to build, and try to build it. If you're interested in web 57 | development, [Luminus](http://www.luminusweb.net/) is a good framework 58 | with good documentation. 59 | 60 | This project is licensed under the [Creative Commons Attribution-NonCommercial 4.0 license.](https://creativecommons.org/licenses/by-nc/4.0/) 61 | -------------------------------------------------------------------------------- /emacs.md: -------------------------------------------------------------------------------- 1 | [Learn more about Brave Clojure On-Site Training](http://www.braveclojure.com/training/) 2 | 3 | ## Movement 4 | 5 | |Keys | Description | 6 | |--------|-------------| 7 | |C-a | Move to beginning of line. | 8 | |M-m | Move to first non-whitespace character on the line. | 9 | |C-e | Move to end of line. | 10 | |C-f | Move forward one character. | 11 | |C-b | Move backward one character. | 12 | |M-f | Move forward one word (I use this a lot). | 13 | |M-b | Move backward one word (I use this a lot, too). | 14 | |C-s | Regex search for text in current buffer and move to it. Press C-s again to move to next match. | 15 | |C-r | Same as C-s, but search in reverse. | 16 | |M-< | Move to beginning of buffer. | 17 | |M-> | Move to end of buffer. | 18 | |M-g g | Go to line. | 19 | 20 | 21 | ## Killing and yanking 22 | 23 | | Keys | Description | 24 | | ----- | ------------| 25 | | C-w | Kill region. | 26 | | M-w | Copy region to kill ring. | 27 | | C-y | Yank. | 28 | | M-y | Cycle through kill ring after yanking. | 29 | | M-d | Kill word. | 30 | | C-k | Kill line. | 31 | 32 | 33 | ## Getting Help 34 | 35 | | Keys | Description | 36 | | ----- | ------------| 37 | | C-h k | key-binding Describe the function bound to the key binding. To get this to work, you actually perform the key sequence after typing C-h k. | 38 | | C-h f | Describe function. | 39 | 40 | 41 | ## Working with frames and windows 42 | 43 | | Keys | Description | 44 | | ----- | ------------| 45 | | C-x o | Switch cursor to another window. Try this now to switch between your Clojure file and the REPL. | 46 | | C-x 1 | Delete all other windows, leaving only the current window in the frame. This doesn’t close your buffers, and it won’t cause you to lose any work. | 47 | | C-x 2 | Split frame above and below. | 48 | | C-x 3 | Split frame side by side. | 49 | | C-x 0 | Delete current window. | 50 | 51 | ## Clojure Buffer key bindings 52 | 53 | | Keys | Description | 54 | | ----- | ------------| 55 | | C-c M-n | Switch to namespace of current buffer. | 56 | | C-x C-e | Evaluate expression immediately preceding point. | 57 | | C-c C-k | Compile current buffer. | 58 | | C-c C-d C-d | Display documentation for symbol under point. | 59 | | M-. and M-, | Navigate to source code for symbol under point and return to your original buffer. | 60 | | C-c C-d C-a | Apropros search; find arbitrary text across function names and documentation. | 61 | 62 | ## CIDER buffer key bindings 63 | 64 | | Keys | Description | 65 | | ----------|------------ | 66 | | C-↑, C-↓ | Cycle through REPL history. | 67 | | C-enter | Close parentheses and evaluate. | 68 | 69 | ## Paredit key bindings 70 | 71 | | Keys | Description | 72 | | ----- | ------------| 73 | | M-x paredit-mode | Toggle paredit mode. | 74 | | M-( | Surround expression after point in parentheses (paredit-wrap-round). | 75 | | C-→ | Slurp; move closing parenthesis to the right to include next expression. | 76 | | C-← | Barf; move closing parenthesis to the left to exclude last expression. | 77 | | C-M-f, C-M-b | Move to the opening/closing parenthesis. | 78 | -------------------------------------------------------------------------------- /exercises/project.clj: -------------------------------------------------------------------------------- 1 | (defproject training "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.seleniumhq.selenium/selenium-java "2.53.0"] 8 | [org.seleniumhq.selenium/htmlunit-driver "2.21"] 9 | [cheshire "5.6.1"] 10 | [hiccup "1.0.5"] 11 | [prismatic/schema "1.1.1"]]) 12 | -------------------------------------------------------------------------------- /exercises/resources/ex08-movie-time/ratings.list: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braveclojure/training/5b7fb9059c17b2166c2e66850094f424319e55eb/exercises/resources/ex08-movie-time/ratings.list -------------------------------------------------------------------------------- /exercises/src/training/core.clj: -------------------------------------------------------------------------------- 1 | (ns training.core) 2 | 3 | (defn foo 4 | "I don't do a whole lot." 5 | [x] 6 | (println x "Hello, World!")) 7 | 8 | (defn multiply 9 | [x y] 10 | (if (= x 1) 11 | y 12 | (+ (multiply (dec x) y) y))) 13 | 14 | (defn triangular-number 15 | [x]) 16 | 17 | (defn factorial 18 | [n] 19 | (if (= n 1) 20 | 1 21 | (* n (factorial (dec n))))) 22 | 23 | (defn factorial 24 | ([n] (factorial n n)) 25 | ([n acc] 26 | (if (= n 1) 27 | acc 28 | (let [n-1 (dec n)] 29 | (recur n-1 (* acc n-1)))))) 30 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ch01_find_the_gap.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ch01-find-the-gap) 2 | 3 | ;; Find the smallest natural number not in a given finite set of 4 | ;; natural numbers. Suppose the numbers are unsorted: 5 | 6 | (def numbers [8 23 9 0 12 11 1 10 13 7 41 4 14 21 5 17 3 19 2 6]) 7 | 8 | ;; How can you find the number in linear time? You can't sort because 9 | ;; sorting can't be done in linear time. 10 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex01_total_word_count.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex01-total-word-count 2 | (:require [clojure.string :as s])) 3 | 4 | ;; Given a string, count the total number of words it has 5 | ;; Example: 6 | 7 | ;; (total-words "this has four words") 8 | ;; ;=> 4 9 | 10 | ;; hint: use s/split with the regex #" " to split the string on 11 | ;; spaces, like this: 12 | (s/split "test string" #" ") 13 | 14 | (defn total-words 15 | [x] 16 | ) 17 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex02_using_data_types.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex02-using-data-types 2 | (:require [clojure.string :as str] 3 | [clojure.set :as set])) 4 | 5 | ;; The cheatsheet is one of your best friends 6 | ;; http://clojure.org/api/cheatsheet 7 | 8 | ;; ======================================== 9 | ;; Operations / Expressions 10 | ;; ======================================== 11 | 12 | (str "str is an operator that takes string" " arugments and concatenates them") 13 | 14 | 15 | 16 | ;; ======================================== 17 | ;; Control flow 18 | ;; ======================================== 19 | 20 | (if true 21 | "Verily your word is your bond" 22 | "Knave! Liar! Cur!") 23 | 24 | (if true 25 | (do (println "Do wraps up multiple expressions") 26 | (println "It's useful for expressions like if"))) 27 | 28 | (if false 29 | "evaluates to nil when no false expression provided") 30 | 31 | ;; Anything not false or nil is truthy 32 | (if "bears eat beets" 33 | "bears beets Battlestar Galactica") 34 | 35 | ;; Use `when` when you only care about the true condition. There's an 36 | ;; implicit `do` so you can evaluate multiple side-effecting 37 | ;; expressions. 38 | (when true 39 | (println "Do wraps up multiple expressions") 40 | (println "It's useful for expressions like if")) 41 | 42 | ;; Boolean operations 43 | (= "a" "a" "a") 44 | (= 0 0) 45 | 46 | (> 1 0) 47 | (>= 1 1) 48 | 49 | ;; Or evaluates to the first truthy value, or nil 50 | (or :red) 51 | (or false nil :large_I_mean_venti :why_cant_I_just_say_large) 52 | 53 | ;; And evalutes to the first falsey value, or the last truthy value 54 | (and :free-wifi :hot-coffee) 55 | (and nil false) 56 | 57 | ;; cond let's you test multiple conditions 58 | (cond (= 1 0) "wow one equals zero" 59 | (= 1 1) "one equals one" 60 | :else "is nothing true!?!?!?") 61 | 62 | ;; ======================================== 63 | ;; def and let 64 | ;; ======================================== 65 | 66 | ;; bind a symbol to a value 67 | (def failed-protagonist-names 68 | ["Larry Potter" "Doreen the Explorer" "The Incredible Bulk"]) 69 | (first failed-protagonist-names) 70 | 71 | ;; happens within a namespace 72 | training.exercises.ex02-using-data-types/failed-protagonist-names 73 | 74 | ;; This throws an exception because the namespace is wrong: 75 | ;; clojure.core/failed-protagonist-names 76 | 77 | ;; bind symbol to a value within a new scope 78 | (let [failed-protagonist-names ["The Human Porch" "Arachnid Man"]] 79 | (first failed-protagonist-names)) 80 | 81 | ;; useful for 'saving' derived values 82 | (let [x (first failed-protagonist-names)] 83 | (println "working with" x) 84 | (str/upper-case x)) 85 | 86 | ;; You try: 87 | ;; * Use def, then use let to 'shadow' the def'd symbol 88 | 89 | 90 | 91 | ;; ======================================== 92 | ;; Numbers & Arithmetic 93 | ;; ======================================== 94 | 95 | (/ 3 5) 96 | (- 10 9 8 7) 97 | 98 | ;; You try: 99 | ;; * Add 5 numbers 100 | ;; * Multiply 3 numbers 101 | 102 | 103 | 104 | ;; ======================================== 105 | ;; Strings 106 | ;; ======================================== 107 | 108 | (str/lower-case "And IIIIeeeIIII will always love yoooou") 109 | 110 | ;; You try: 111 | ;; * upper-case a line from your favorite song 112 | 113 | 114 | 115 | ;; ======================================== 116 | ;; Keywords 117 | ;; ======================================== 118 | ;; * Often used in maps 119 | ;; * Can use as a function 120 | 121 | :this-is-a-keyword 122 | :? ; <= that's a keyword too 123 | (:first-name {:first-name ""}) 124 | 125 | 126 | 127 | ;; ======================================== 128 | ;; Maps 129 | ;; ======================================== 130 | 131 | {:username "blambledirk" 132 | :email "blambledirk@gmail.com" 133 | :failed-login-attempts 3}, 134 | 135 | ;; keys can be of any type 136 | {["failures" 1] #{"blambledirk"} 137 | ["failures" 10] #{"crundlemink"}} 138 | 139 | ;; Let's do things to a map 140 | (def personal-info 141 | {:address {:street "10 Bumbleberry Boulevard" 142 | :city "Bananapants"} 143 | :petname "Rocky"}) 144 | (get personal-info :address) 145 | (get-in personal-info [:address :city]) 146 | (assoc-in personal-info [:address :city] "Barnaclecrab") 147 | (dissoc personal-info :address) 148 | (keys personal-info) 149 | (vals personal-info) 150 | 151 | (hash-map :key1 1 :key2 2) 152 | (into {} [[:key 1] [:key2 2]]) 153 | (conj {:a 1} [:b 2]) ;; you're going to see conj a lot 154 | 155 | (count {:a 1 :b 2}) 156 | (empty? {}) 157 | (empty? {:a 1}) 158 | (not-empty {}) 159 | 160 | ;; You try: 161 | ;; * Create a map that represents someone's name 162 | ;; * Now create a nested map with :name as a key and a 163 | ;; map representing someone's name as the value 164 | ;; * Use get to retrieve the name map 165 | ;; * Use get-in to get just the first name 166 | ;; * Use assoc-in to 'change' the first name 167 | 168 | 169 | 170 | ;; ======================================== 171 | ;; Vectors 172 | ;; ======================================== 173 | 174 | [0 "foo" :bar] 175 | (vector 1 2 3) 176 | (vec {:a 1}) 177 | (into [] '(1 2 3)) 178 | (conj [1 2 3] 4) ;; appends element to "business end" of data structure 179 | (nth [:a :b :c] 0) 180 | (cons 1 [2 3 4]) ;; appends arg to front of data structure 181 | 182 | (empty? []) 183 | (not-empty []) 184 | 185 | (concat [:a :b] [:c :d]) 186 | 187 | ;; Try these functions: 188 | ;; * first 189 | ;; * rest 190 | ;; * take 191 | ;; * assoc 192 | ;; * nth 193 | 194 | 195 | 196 | ;; ======================================== 197 | ;; Exercise: Points 198 | ;; ======================================== 199 | ;; * Represent a point on plane 200 | 201 | 202 | 203 | ;; ======================================== 204 | ;; Lists 205 | ;; ======================================== 206 | 207 | ;; For literals, precede with a quote 208 | '(whistle while you work) 209 | '(0 "foo" :bar) 210 | (list 0 "foo" :bar) 211 | (into '(0 "foo" :bar) [:baz]) 212 | (conj '(0 "foo" :bar) :baz) 213 | 214 | (concat '(:a :b) '(:c :d)) 215 | 216 | ;; Try these functions: 217 | ;; * first 218 | ;; * rest 219 | ;; * empty? 220 | 221 | ;; ======================================== 222 | ;; Sets 223 | ;; ======================================== 224 | 225 | #{:pizza :fries :cholesterol} 226 | (set [:pizza :fries :cholesterol]) 227 | (hash-set :pizza :fries :cholesterol) 228 | 229 | (into #{:pizza :fries :cholesterol} [:pizza :burgers]) 230 | (conj #{:pizza} :pizza) 231 | 232 | (count #{:pizza}) 233 | (empty? #{:pizza}) 234 | 235 | (set/union #{:pizza} #{:fries}) 236 | (set/intersection #{:pizza :fries} #{:pizza :burgers}) 237 | 238 | (#{:pizza :fries} :pizza) 239 | (#{:pizza :fries} :cholesterol) 240 | 241 | (filter #{:pizza :fries} [:pizza :fries :cholesterol :pizza]) 242 | 243 | ;; Try this: 244 | ;; * Use the set/difference function 245 | ;; * Convert a set to a vector 246 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex03_banned_words.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex03-banned-words 2 | (:require [clojure.string :as str 3 | clojure.set :as set])) 4 | 5 | ;; Your mission is to write a function that takes a string and returns 6 | ;; true if it contains banned words 7 | 8 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex04_function_basics.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex04-function-basics) 2 | 3 | ;; ======================================== 4 | ;; Function expressions 5 | ;; ======================================== 6 | 7 | (+ 1 2) 8 | ((or + -) 1 2) 9 | ((or +) 1 2) 10 | 11 | 12 | ;; ======================================== 13 | ;; Defining functions 14 | ;; ======================================== 15 | 16 | (defn your-fn 17 | "docstring" 18 | [arg1 arg2 & args]) 19 | 20 | (defn no-params 21 | [] 22 | "This function doesn't want your input") 23 | 24 | (defn one-param 25 | [x] 26 | (str x " is the best thing in the world!")) 27 | 28 | (defn rest-param 29 | [& args] 30 | args) 31 | (rest-param 1 2 3) 32 | 33 | ;; multi-arity function 34 | 35 | (defn resize-image 36 | "With 2-artiy, resize an image so that the larger dimension is equal 37 | to second argument. With 3-arity, specify width and height" 38 | ([image box] 39 | (let [scale (/ box (max (:width image) (:height image))) 40 | width (* scale (:width image)) 41 | height (* scale (:height image))] 42 | (resize-image image width height))) 43 | ([image width height] 44 | {:width width :height height})) 45 | 46 | ;; Exercise: write a function number-max that compares two numbers and returns 47 | ;; the larger. 48 | 49 | 50 | 51 | ;; ======================================== 52 | ;; Functions are values 53 | ;; ======================================== 54 | 55 | (map inc [1 2 3]) 56 | ((complement empty?) [1 2 3]) 57 | 58 | ;; Bind functions to symbols just like any other value 59 | (def occupied? (complement empty?)) 60 | (occupied? [1 2 3]) 61 | 62 | ;; Take a function as an argument, just like data 63 | (defn mathochist 64 | [x op y] 65 | (op x y)) 66 | (mathochist 1 / 3) 67 | 68 | 69 | 70 | ;; ======================================== 71 | ;; Anonymous functions 72 | ;; ======================================== 73 | ;; Subtitle: built-in functions aren't special! 74 | 75 | (fn [x] (+ x 1)) 76 | #(+ % 1) 77 | 78 | ;; note to self: mention closures 79 | (defn inc-maker 80 | [n] 81 | #(+ % n)) 82 | ((inc-maker 3) 5) 83 | 84 | ;; Bind anonymous functions 85 | (def inc-5 (inc-maker 5)) 86 | (inc-5 10) 87 | (def inc-10 #(+ % 10)) 88 | 89 | ;; Pass anonymous functions as arguments 90 | (filter even? 91 | [0 1 2 3 4]) 92 | 93 | (filter #(= 0 (mod % 3)) 94 | [0 1 2 3 4]) 95 | 96 | 97 | 98 | ;; ======================================== 99 | ;; Recursion 100 | ;; ======================================== 101 | 102 | ;; functions can call themselves 103 | (defn factorial 104 | [n] 105 | (if (= n 1) 1 106 | (* n (factorial (dec n))))) 107 | 108 | ;; recur will call the function recursively without consuming stack 109 | (defn tail-recursive-factorial 110 | ([n] (tail-recursive-factorial n 1)) 111 | ([n acc] 112 | (if (= n 1) acc 113 | (recur (dec n) (* n acc))))) 114 | 115 | ;; You can also do recursive calls with loop/recur 116 | (loop [n 1 117 | sum 0] 118 | (if (= n 6) 119 | sum 120 | (recur (inc n) (+ sum n)))) 121 | 122 | 123 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex05_odds.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex05-odds) 2 | 3 | ;; Write a function (and any helper functions) that calculates the 4 | ;; odds of a sum being returned when two n-sided dice are thrown. 5 | ;; 6 | ;; Examples: 7 | ;; (odds 3 6 6) => 1/18 8 | ;; (odds 3 4 4) => 1/6 9 | ;; 10 | ;; In both these examples, 3 is the sum. The other two numbers are the 11 | ;; number of sides the dice have. 12 | ;; 13 | ;; It's definitely doable using just the functions and operators that 14 | ;; you've seen so far. Have fun :) 15 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex06_more_syntax.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex06-more-syntax) 2 | 3 | ;; ======================================== 4 | ;; Exceptions 5 | ;; ======================================== 6 | 7 | (try (/ 1 0) 8 | (catch Exception e 9 | (str "caught exception: " (.getMessage e)))) 10 | 11 | 12 | ;; ======================================== 13 | ;; Destructuring 14 | ;; ======================================== 15 | ;; Destructuring is incredibly useful, but it's not absolutely 16 | ;; necessary so I'm not going to spend much time on it. There's an 17 | ;; excellent guide at http://clojure.org/guides/destructuring 18 | 19 | ;; positional destructuring 20 | (def numbers [1 2 3 4 5]) 21 | (let [[a b] numbers] 22 | a) 23 | 24 | (let [[a b] numbers] 25 | b) 26 | 27 | (let [[a b] numbers] 28 | (+ a b)) 29 | 30 | (let [[a b & c] [1 2 3 4 5]] 31 | c) 32 | 33 | ;; map destructuring 34 | (def image {:width 10 :height 30}) 35 | (let [{:keys [width height]} image] 36 | (* width height)) 37 | 38 | 39 | (defn format-coords 40 | [{:keys [lat lng] :as point}] 41 | (str "lat " lat ", lng " lng " in " point)) 42 | 43 | ;; Try this: destructure the following 44 | 45 | ;; Use destructuring to reference the first and second elements of 46 | ;; this vector and addthem 47 | [0 1 2 3 4] 48 | 49 | ;; Use destructuring to create the string "Jebediah is a butter churner" 50 | {:name "Jebediah" 51 | :occupation "butter churner"} 52 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex07_core_functions.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex07-core-functions) 2 | 3 | ;; ======================================== 4 | ;; Data functions 5 | ;; ======================================== 6 | ;; You use these all the time 7 | 8 | (map inc [1 2 3 4]) 9 | (map :height [{:width 10 :height 20} {:width 30 :height 40}]) 10 | 11 | (filter even? [1 2 3 4]) 12 | 13 | (def aria-stark {:faceless true :name false}) 14 | (def sansa-stark {:name "Sansa"}) 15 | (filter :name [aria-stark sansa-stark]) 16 | 17 | (def images [{:dims {:w 10 :h 30}} 18 | {:dims {:w 23 :h 45}}]) 19 | (filter #(> (get-in % [:dims :w]) 20 | 20) 21 | images) 22 | 23 | (sort [0 3 5 1]) 24 | (reverse (sort [0 3 5 1])) 25 | 26 | (sort-by #(get-in % [:dims :w]) images) 27 | 28 | (reduce + [1 23 4]) 29 | (reduce merge 30 | {} 31 | [{:height 10} {:width 20} {:depth 30}]) 32 | 33 | (first [1 2 3]) 34 | (rest [1 2 3]) 35 | (conj [1 2 3] 4) 36 | (cons 4 [1 2 3]) 37 | 38 | (first '(1 2 3)) 39 | (rest '(1 2 3)) 40 | (conj '(1 2 3) 4) 41 | (cons 4 '(1 2 3)) 42 | 43 | ;; slide time! 44 | 45 | ;; These functions can all be implemented in terms of first, rest, and 46 | ;; cons 47 | (defn filter' 48 | [pred xs] 49 | (if (empty? xs) 50 | xs 51 | (if (pred (first xs)) 52 | (cons (first xs) 53 | (filter' pred (rest xs))) 54 | (filter' pred (rest xs))))) 55 | 56 | ;; You try: 57 | ;; * Use map and filter together: first map a vector, then filter it 58 | ;; * Use reduce to select the largest number from a vector 59 | ;; * Implement map in terms of first, rest, and cons 60 | 61 | 62 | 63 | ;; ======================================== 64 | ;; Function functions 65 | ;; ======================================== 66 | 67 | ;; complement 68 | (def odd?' (complement even?)) 69 | 70 | (odd?' 1) 71 | (odd?' 2) 72 | 73 | ;; apply 74 | (max 1 2 3) 75 | (max [1 2 3]) 76 | (apply max [1 2 3]) 77 | (apply merge [{:width 10} {:height 20}]) 78 | 79 | ;; partial 80 | (def inc3 (partial + 3)) 81 | (inc3 5) 82 | 83 | (def default-attrs (partial merge {:width 10 :height 20})) 84 | (default-attrs {:width 15 :depth 30} {:name "Farfarf"}) 85 | 86 | ;; comp 87 | (def strinc (comp str inc)) 88 | (strinc 3) 89 | 90 | (def first-sample (comp first :samples)) 91 | (first-sample {:samples [10.3 5.2]}) 92 | 93 | ;; naive way to implement comp 94 | (defn lousy-comp 95 | [f2 f1] 96 | (fn [x] (f2 (f1 x)))) 97 | (def occupied? (comp not empty?)) 98 | (occupied? [3]) 99 | (occupied? []) 100 | 101 | ;; You try: 102 | ;; * Implement complement 103 | ;; * Implement partial 104 | ;; Hint: use apply 105 | 106 | 107 | 108 | ;; ======================================== 109 | ;; Laziness 110 | ;; ======================================== 111 | ;; Map & filter are lazy 112 | ;; Some functions like reduce are greedy 113 | 114 | ;; range produces a lazy seq of numbers 115 | (range 10) 116 | 117 | (map inc (range 1000000)) ; doesn't actually map yet 118 | 119 | (take 10 (map inc (range 1000000))) ; performs map on first 32 120 | 121 | ;; infinite sequences 122 | 123 | (range) ; infinite sequence of integers starting at 0 124 | 125 | 126 | ;; You try: 127 | ;; * Create a lazy sequence of all even numbers 128 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex08_movie_time.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex08-movie-time 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as str])) 4 | 5 | ;; This function includes some stuff we haven't larned yet; don't 6 | ;; worry about that for now. We only need to use this to load the 7 | ;; movie data. 8 | (defn get-movies 9 | [] 10 | (->> (io/resource "ex08-movie-time/ratings.list") 11 | slurp 12 | str/split-lines 13 | (map #(str/split % #"\s{2,}")) 14 | (map (fn [[_ _ votes rating title]] 15 | {:votes (Integer. votes) 16 | :rating (Double. rating) 17 | :title (str/replace title #"\"" "")})))) 18 | 19 | (def movies (get-movies)) 20 | ;; Each movie is a map like: 21 | {:votes 4130005 22 | :rating 8.3 23 | :title "Tears of a Clown"} 24 | 25 | 26 | ;; Exercises: 27 | ;; * Get the highest ratest movie with more than 1000 votes. 28 | ;; Extra credit: to break a tie, select the title with more votes. 29 | ;; * Get all movies rated above x with at least y votes, sorted by rating. 30 | ; WARNNIG: make sure to use a high minimum vote count or your REPL will freeze 31 | ;; while it tries to print all the results 32 | ;; * Get a count of all movies rated above x and below y with at least z votes 33 | ;; * Calculate the average movie rating across all movies 34 | 35 | ;; Tips: 36 | ;; * Don't just type `movies` in your REPL, it will attempt to 37 | ; print all of them and there are thousands 38 | ;; * However, you can interact with the movies data with functions 39 | ;; like `(first movies)`, `(count movies)` 40 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex09_review_core.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex09-review-core) 2 | 3 | ;; You try: 4 | ;; * Convert a vector to a set 5 | ;; * Use assoc on a map 6 | ;; * Implement complement 7 | ;; * Implement comp 8 | 9 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex10_functional_programming.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex10-functional-programming 2 | (:require [training.exercises.ex08-movie-time :as mt] 3 | [clojure.string :as str] 4 | [clojure.string :as s])) 5 | 6 | ;; ======================================== 7 | ;; Pure functions 8 | ;; ======================================== 9 | 10 | ;; no side effects 11 | (+ 1 3) 12 | 13 | ;; associng a map doesn't change the original map 14 | (let [x {:name "Dr. Jekyll"}] 15 | (println (assoc x :name "Mr. Hyde")) 16 | (println x)) 17 | 18 | 19 | ;; referential transparency 20 | (clojure.string/upper-case "bob loblaw's law blog") 21 | 22 | ;; not referentially transparent 23 | (rand-int 10) 24 | 25 | 26 | 27 | ;; ======================================== 28 | ;; Immutability 29 | ;; ======================================== 30 | ;; * Main data types are immutable (vectors, maps, lists, sets) 31 | 32 | ;; adding an element to a vector doesn't change it 33 | (let [x [1 2 3]] 34 | (println (conj x 4)) 35 | (println x)) 36 | 37 | 38 | 39 | ;; ======================================== 40 | ;; Recursion instead of loop/for 41 | ;; ======================================== 42 | 43 | (defn sum 44 | ([vals] 45 | (sum vals 0)) 46 | ([vals accumulating-total] 47 | (if (empty? vals) 48 | accumulating-total 49 | (recur (rest vals) (+ (first vals) accumulating-total))))) 50 | 51 | ;; Recursive call, step-by-step 52 | (sum [39 5 1]) ; single-arity body calls two-arity body 53 | (sum [39 5 1] 0) 54 | (sum [5 1] 39) 55 | (sum [1] 44) 56 | (sum [] 45) ; base case is reached, so return accumulating-total 57 | ; => 45 58 | 59 | ;; map, reduce, etc all recursive 60 | (reduce + [39 5 1]) 61 | 62 | ;; multiple seq operations to control result 63 | ;; total number of votes for all movies of n rating 64 | (reduce + 65 | (map :votes 66 | (filter #(= 8.0 (:rating %)) mt/movies))) 67 | 68 | 69 | 70 | ;; ======================================== 71 | ;; Function composition instead of attribute mutation 72 | ;; ======================================== 73 | 74 | ;; Pass a value through a chain of functions 75 | ;; reusable: can be used on any string 76 | (defn clean 77 | [text] 78 | (str/replace (str/trim text) #"lol" "LOL")) 79 | 80 | (clean "My boa consitrctor is so sassy lol!") 81 | 82 | ;; Function composition also used to traverse a structure 83 | (:title (first mt/movies)) 84 | 85 | (def first-title (comp :title first)) 86 | 87 | (def character 88 | {:name "Smooches McCutes" 89 | :attributes {:intelligence 10 90 | :strength 4 91 | :dexterity 5}}) 92 | (def c-int (comp :intelligence :attributes)) 93 | (def c-str (comp :strength :attributes)) 94 | (def c-dex (comp :dexterity :attributes)) 95 | 96 | (c-int character) 97 | (c-str character) 98 | (c-dex character) 99 | 100 | 101 | 102 | ;; ======================================== 103 | ;; Make it more readable with threading 104 | ;; ======================================== 105 | ;; Also known as the stabby macro 106 | 107 | ;; First-position threading; equivalent to `clean` above 108 | (-> "My boa consitrctor is so sassy lol!" 109 | s/trim 110 | (str/replace #"lol" "LOL")) 111 | 112 | ;; Last-position threading; notice two `>` 113 | (filter even? (map inc [1 2 3])) 114 | ;; threaded: 115 | (->> [1 2 3] 116 | (map inc) 117 | (filter even?)) 118 | 119 | 120 | ;; You try: re-write this using threading 121 | (reduce + 122 | (map :votes 123 | (filter #(= 8.0 (:rating %)) mt/movies))) 124 | 125 | 126 | 127 | ;; ======================================== 128 | ;; More functional programming 129 | ;; ======================================== 130 | 131 | ;; Let's update Smooches McCutes, increment strength 132 | (assoc-in character 133 | [:attributes :strength] 134 | (inc (get-in character [:attributes :strength]))) 135 | 136 | (update-in character [:attributes :strength] inc) 137 | 138 | (assoc-in character 139 | [:attributes :strength] 140 | (+ (get-in character [:attributes :strength]) 2)) 141 | 142 | (update-in character [:attributes :strength] + 2) 143 | 144 | ;; Also works on vectors 145 | (update-in [5 6] [0] inc) 146 | 147 | ;; Here's how you could implement update-in. 148 | (defn update-in' 149 | [m path f & args] 150 | (let [current (get-in m path) 151 | new (apply f current args)] 152 | (assoc-in m path new))) 153 | 154 | ;; Because Clojure focuses on using a small set of data abstractions, 155 | ;; you end up with a lot of small, widely-apaplicable utility 156 | ;; functions like this. 157 | 158 | ;; Returns sub-collections, splitting each time odd? returns a new 159 | ;; value 160 | (partition-by odd? [1 1 1 2 2 3 3]) 161 | 162 | (partition-by count ["a" "b" "ab" "ac" "c"]) 163 | 164 | 165 | 166 | ;; ======================================== 167 | ;; Programming to abstractions 168 | ;; ======================================== 169 | 170 | ;; The seq abstraction 171 | (first [1 2 3]) 172 | (first '(1 2 3)) 173 | (first #{1 2 3}) 174 | (first {:a 1 :b 2 :c 3}) 175 | 176 | (rest [1 2 3]) 177 | (rest '(1 2 3)) 178 | 179 | (filter odd? [1 2 3]) 180 | (filter odd? #{1 2 3}) 181 | ;; sometimes you have to convert back 182 | (set (filter odd? #{1 2 3})) 183 | 184 | ;; The associative abstraction 185 | (get {:a 1} :a) 186 | (get [:a :b] 1) 187 | 188 | (assoc {:a 1} :a 2) 189 | (assoc [:a :b] 1 :c) 190 | 191 | ;; The collection abstraction 192 | (count [1 2 3]) 193 | (count #{1 2 3}) 194 | 195 | (empty? {}) 196 | 197 | (some odd? [1 2 3]) 198 | (some odd? [2 2 2]) 199 | 200 | (every? odd? [1 2 3]) 201 | (every? odd? #{1 3}) 202 | 203 | (distinct? [1 2 3]) 204 | (distinct? {:a 1 :b 2}) 205 | (distinct [1 2 2]) 206 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex10_functional_programming.js: -------------------------------------------------------------------------------- 1 | // ======================================== 2 | // Recursion instead of loop/for 3 | // ======================================== 4 | 5 | // In JS and other imperative languages you often do something like 6 | // this: 7 | var dailySteps = [2000 5002 6340 10001]; 8 | var l = dailySteps.length; 9 | var totalSteps = 0; 10 | 11 | // sum 12 | for (var i = 0; i < l; i++) { 13 | totalSteps += dailySteps[i]; 14 | } 15 | 16 | // or this: 17 | var stepDays = [ 18 | {"day": 1, "steps": 2000}, 19 | {"day": 2, "steps": 5002}, 20 | {"day": 3, "steps": 6340}, 21 | {"day": 4, "steps": 10001}, 22 | ]; 23 | var l = stepDays.length; 24 | var goodDays = [] 25 | 26 | // filter 27 | for (var i = 0; i < l; i++) { 28 | if stepDays[i].steps > 6000 { 29 | goodDays.push(stepDays[i]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex11_read_eval_macros.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex11-read-eval-macros) 2 | 3 | ;; ======================================== 4 | ;; Read and Eval 5 | ;; ======================================== 6 | 7 | ;; The reader reads text to produce a Clojure data structure. When you 8 | ;; write Clojure, you're writing text that represents data structures. 9 | (read-string "(+ 1 2)") 10 | (= '(+ 1 2) (read-string "(+ 1 2)")) 11 | (= 3 (read-string "(+ 1 2)")) 12 | 13 | ;; The evaluator evaluates those data structures 14 | (eval (read-string "(+ 1 2)")) 15 | (eval '(+ 1 2)) 16 | 17 | ;; What's with the '? 18 | map 19 | 'map 20 | (quote map) 21 | 22 | (map inc [1 2 3]) 23 | '(map inc [1 2 3]) 24 | 25 | ;; You can manipulate data structures before they get evald 26 | (eval (list '+ 1 2)) 27 | 28 | (defn infix 29 | [expr] 30 | (let [x (first expr) 31 | op (second expr) 32 | y (last expr)] 33 | (list op x y))) 34 | 35 | (eval (infix '(1 + 2))) 36 | 37 | ;; aside: you can use destructuring 38 | (defn infix' 39 | [[x op y]] 40 | (list op x y)) 41 | 42 | ;; Macros let you manipulate the data structures emitted by the reader, 43 | ;; sending the result to the evaluator 44 | (defmacro infix-macro 45 | [x op y] 46 | (list op x y)) 47 | 48 | (infix-macro 1 + 2) 49 | 50 | ;; You try: 51 | ;; * Write some code to handle postfix evaluation, like: 52 | ;; (eval (postfix 1 2 +)) 53 | 54 | 55 | ;; ======================================== 56 | ;; Eval rules 57 | ;; ======================================== 58 | 59 | ;; Data that's not a list or symbol evals to itself: 60 | (eval true) 61 | (eval false) 62 | (eval {}) 63 | (eval 1) 64 | (eval #{1 2}) 65 | ;; empty lists also eval to themselves 66 | (eval ()) 67 | 68 | 69 | ;;;; 70 | ;; Let's get to know symbols! 71 | ;;;; 72 | ;; In general, Clojure resolves a symbol by: 73 | ;; 74 | ;; 1. Looking up whether the symbol names a special form. If it doesn’t . . . 75 | ;; 2. Looking up whether the symbol corresponds to a local binding. If it doesn’t . . . 76 | ;; 3. Trying to find a namespace mapping introduced by def. If it doesn’t . . . 77 | ;; 4. Throwing an exception 78 | 79 | ;; if is a special form 80 | (if true :a :b) 81 | 82 | (let [x 5] 83 | (+ x 3)) 84 | 85 | (def x 15) 86 | x 87 | 88 | (let [x 5] 89 | (let [x 6] 90 | (+ x 3))) 91 | 92 | (defn exclaim 93 | [exclamation] 94 | (str exclamation "!")) 95 | 96 | (read-string "+") 97 | (type (read-string "+")) 98 | (list (read-string "+") 1 2) 99 | (eval (list (read-string "+") 1 2)) 100 | 101 | ;; Evaling lists 102 | ;; function calls 103 | (+ 1 2) 104 | 105 | ;; special forms 106 | (if true 1 2) 107 | 108 | ;; Evaling macros 109 | (read-string "(1 + 1)") 110 | 111 | ;; Why will this fail? 112 | (comment (eval (read-string "(1 + 1)"))) 113 | 114 | ;; You can manipulate the data before evaling it 115 | (let [infix (read-string "(1 + 1)")] 116 | (list (second infix) (first infix) (last infix))) 117 | 118 | 119 | ;; ======================================== 120 | ;; Writing macros 121 | ;; ======================================== 122 | 123 | ;; Macro anatomy 124 | ;; 1. defmacro 125 | ;; 2. macro name 126 | ;; 3. macro arguments. When the macro is called, 127 | ;; these arguments are unevaluated data. 128 | ;; 4. macro body - works exactly like a function body 129 | (defmacro infix-m 130 | [[x op y]] 131 | (list op x y)) 132 | 133 | 134 | ;; Macros have to return a list. Why doesn't this work? 135 | (defmacro broken-infix 136 | [[x op y]] 137 | (op x y)) 138 | 139 | (broken-infix (1 + 2)) 140 | ;; This doesn't work because, in the macro body, you're applying `op` 141 | ;; to the `x` and `y`, not returning the list '(op x y). The return 142 | ;; value of the macro is ('+ 1 2), which attempts to apply the plus 143 | ;; _symbol_ to the arguments 1 and 2, and the return value of that is 144 | ;; 2. 2 is then passed to the evaluator. 145 | ;; 146 | ;; Instead, you want the macro to return the list '(+ 1 2) so that the 147 | ;; evaluator will handle it correctly. 148 | 149 | 150 | ;; Check macros with macroexpand and macroexpand-1: 151 | (macroexpand-1 '(broken-infix (1 + 2))) 152 | (macroexpand-1 '(when true (pr "when") (pr "true"))) 153 | 154 | ;; simple quoting 155 | (defmacro when' 156 | "Evaluates test. If logical true, evaluates body in an implicit do." 157 | {:added "1.0"} 158 | [test & body] 159 | (list 'if test (cons 'do body))) 160 | 161 | 162 | 163 | ;; ======================================== 164 | ;; Syntax Quoting 165 | ;; ======================================== 166 | ;; * Uses fully-qualified symbols 167 | ;; * Allows unquoting and unquote splicing 168 | 169 | ;; Fully-qualified symbols 170 | '+ 171 | `+ 172 | 173 | ;; recursively syntax quotes all elements 174 | `(+ 1 (- 2 3)) 175 | 176 | ;; You can unquote values 177 | ;; When you unqoute something, it's evaluated, and the result is 178 | ;; placed in the resulting data structure returned by syntax quote 179 | (def flibbity :a) 180 | `(get {:a 1} flibbity) 181 | `(get {:a 1} ~flibbity) 182 | 183 | `(+ 1 (inc 1)) 184 | `(+ 1 ~(inc 1)) 185 | 186 | ;; Unquote splicing evaluates a form which should return a sequence, 187 | ;; then "unwraps" it 188 | (defmacro wait 189 | [timeout & body] 190 | `(do (Thread/sleep ~timeout) ~@body)) 191 | (macroexpand-1 '(wait 500 192 | (println "waited!") 193 | (reduce + [1 2 3]))) 194 | 195 | ;; Without unquote splicing, ~body is a list 196 | (defmacro bad-wait 197 | [timeout & body] 198 | `(do (Thread/sleep ~timeout) ~body)) 199 | (macroexpand-1 '(bad-wait 500 200 | (println "waited!") 201 | (reduce + [1 2 3]))) 202 | ;; expands to: 203 | (comment 204 | (do (java.lang.Thread/sleep 500) 205 | ((println "waited!") (reduce + [1 2 3])))) 206 | 207 | 208 | 209 | ;; ======================================== 210 | ;; Macro pitfalls 211 | ;; ======================================== 212 | 213 | ;; Variable capture: macro introduces a binding that shadows an 214 | ;; existing binding 215 | 216 | (def message "Good job!") 217 | ;; This macro will shadow `message` 218 | (defmacro with-mischief 219 | [& stuff-to-do] 220 | (concat (list 'let ['message "Oh, big deal!"]) 221 | stuff-to-do)) 222 | 223 | (with-mischief 224 | (println "Here's how I feel about that thing you did: " message)) 225 | 226 | (macroexpand-1 '(with-mischief 227 | (println "Here's how I feel about that thing you did: " message))) 228 | 229 | ;; get around this with gensyms 230 | (gensym) 231 | (gensym 'message) 232 | 233 | (defmacro without-mischief 234 | [& stuff-to-do] 235 | (let [macro-message (gensym 'message)] 236 | `(let [~macro-message "Oh, big deal!"] 237 | ~@stuff-to-do 238 | (println "I still need to say: " ~macro-message)))) 239 | 240 | (without-mischief 241 | (println "Here's how I feel about that thing you did: " message)) 242 | 243 | ;; autogensyms are a convenience. 244 | ;; All instances of the same autogensym 245 | ;; in a syntax quote evaluate to the same symbol 246 | `(blarg# blarg#) 247 | 248 | (defmacro without-mischief' 249 | [& stuff-to-do] 250 | `(let [message# "Oh, big deal!"] 251 | ~@stuff-to-do 252 | (println "I still need to say: " message#))) 253 | 254 | (macroexpand-1 '(without-mischief' 255 | (println "Here's how I feel about that thing you did: " message))) 256 | ;; expands to: 257 | (comment 258 | (clojure.core/let 259 | [message__21129__auto__ "Oh, big deal!"] 260 | (println "Here's how I feel about that thing you did: " message) 261 | (clojure.core/println "I still need to say: " message__21129__auto__))) 262 | 263 | ;; ======================================== 264 | ;; When to use macros? 265 | ;; ======================================== 266 | 267 | ;; * When you're beginning, use them whenever you feel like it. Then 268 | ;; try to do the same thing with functions. 269 | ;; * Use them when you need new syntax - new evaluation rules not 270 | ;; provided out of the box. Examples: 271 | 272 | (-> "Catface Meowmers" 273 | (clojure.string/lower-case) 274 | (clojure.string/split #" ")) 275 | 276 | (comment 277 | (if-valid 278 | rest-params validation-map errors 279 | false 280 | [true (errors-map errors)])) 281 | 282 | ;; You try: 283 | ;; Remember this? 284 | 285 | (comment 286 | (def character 287 | {:name "Smooches McCutes" 288 | :attributes {:intelligence 10 289 | :strength 4 290 | :dexterity 5}}) 291 | (def c-int (comp :intelligence :attributes)) 292 | (def c-str (comp :strength :attributes)) 293 | (def c-dex (comp :dexterity :attributes))) 294 | 295 | ;; write a macro, defattrs, which lets you define c-int, c-str, c-dex 296 | ;; more succinctly, like this: 297 | 298 | (comment 299 | (defattrs 300 | c-int :intelligence 301 | c-str :strength 302 | c-dex :dexterity) 303 | 304 | (c-int character) ;=> 10 305 | ) 306 | 307 | 308 | ;; Bonus 309 | ;; `and` is a macro: 310 | 311 | (comment 312 | (defmacro and 313 | "Evaluates exprs one at a time, from left to right. If a form 314 | returns logical false (nil or false), and returns that value and 315 | doesn't evaluate any of the other expressions, otherwise it returns 316 | the value of the last expr. (and) returns true." 317 | {:added "1.0"} 318 | ([] true) 319 | ([x] x) 320 | ([x & next] 321 | `(let [and# ~x] 322 | (if and# (and ~@next) and#))))) 323 | 324 | ;; study it till you understand it, and optionally implement or as a macro 325 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex12_java_interop.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex12-java-interop 2 | (:import [java.util Date] 3 | [org.openqa.selenium By WebDriver WebElement] 4 | [org.openqa.selenium.firefox FirefoxDriver])) 5 | 6 | ;; note to self: start with slides 7 | 8 | ;; ======================================== 9 | ;; Java Interop 10 | ;; ======================================== 11 | ;; * There's a direct mapping 12 | 13 | ;; Create an instance of a class 14 | (java.util.Date.) 15 | 16 | ;; Call a method 17 | (let [now (java.util.Date.)] 18 | (.toString now)) 19 | 20 | (.length "omg this is a java string") 21 | 22 | ;; since Date is imported, don't need the entire namespace 23 | (Date.) 24 | 25 | ;; You try: 26 | ;; * get the index of the first occurrence of a substring 27 | ;; https://docs.oracle.com/javase/7/docs/api/java/lang/String.html 28 | 29 | 30 | ;; If you want to do multiple things to an object, there's the `doto` 31 | ;; macro: 32 | (comment 33 | (doto (SimpleEmail.) 34 | (.setHostName "smtp.gmail.com") 35 | (.setSslSmtpPort "465") 36 | (.setSSL true) 37 | (.addTo "you@you.com") 38 | (.setFrom "from-address@whatever.com" "Me me me") 39 | (.setSubject "Subject goes here") 40 | (.setMsg "Some kind of email message") 41 | (.send)) 42 | 43 | ;; above is equivalent to 44 | (let [email (SimpleEMail.)] 45 | (.setHostName email "smtp.gmail.com") 46 | (.setSslSmtpPort email "465"))) 47 | 48 | ;; Call a static method 49 | (Math/abs -3) 50 | 51 | 52 | 53 | ;; ======================================== 54 | ;; Selenium example 55 | ;; ======================================== 56 | 57 | (defn search-for-cheese 58 | [] 59 | (let [driver (FirefoxDriver.)] 60 | (.get driver "http://www.google.com") 61 | 62 | (doto (.findElement driver (By/name "q")) 63 | ;; this into-array bit is one of the annoyances you have to deal 64 | ;; with when working with Java libs 65 | (.sendKeys (into-array String ["Cheese!"])) 66 | (.submit)) 67 | 68 | (Thread/sleep 1000) 69 | 70 | (println "Page title is:" (.getTitle driver)) 71 | 72 | ;; closes the browser 73 | (.quit driver))) 74 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex13_multimethods.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex13-multimethods 2 | (:require [hiccup.core :as h])) 3 | 4 | ;; ======================================== 5 | ;; Multimethods 6 | ;; ======================================== 7 | 8 | (defmulti full-moon-behavior 9 | (fn [were-creature] (:were-type were-creature))) 10 | 11 | (defmethod full-moon-behavior :wolf 12 | [were-creature] 13 | (str (:name were-creature) " will howl and murder")) 14 | 15 | (defmethod full-moon-behavior :simmons 16 | [were-creature] 17 | (str (:name were-creature) " will encourage people and sweat to the oldies")) 18 | 19 | (full-moon-behavior {:were-type :wolf 20 | :name "Rachel from next door"}) 21 | 22 | (full-moon-behavior {:name "Andy the baker" 23 | :were-type :simmons}) 24 | 25 | 26 | 27 | (def moderator-usernames ["bob" "divya" "grogthor, intergalactic warlord"]) 28 | (defmulti moderator? class) 29 | (defmethod moderator? String 30 | [username] (some #(= % username) moderator-usernames)) 31 | (defmethod moderator? clojure.lang.IPersistentMap 32 | [m] (moderator? (:username m))) 33 | 34 | 35 | 36 | ;; can dispatch on any transformation of any or all arguments 37 | ;; say what!? 38 | (defmulti enterprise-readiness 39 | (fn [person briggs-meyer] briggs-meyer)) 40 | 41 | (defmethod enterprise-readiness ["e" "n" "t" "j"] 42 | [person briggs-meyer] 43 | (str (:name person) " is enterprise ready!")) 44 | 45 | (defmethod enterprise-readiness ["i" "s" "f" "p"] 46 | [person briggs-meyer] 47 | (str (:name person) " is not enterprise ready!")) 48 | 49 | (enterprise-readiness {:name "Bubba"} ["i" "s" "f" "p"]) 50 | 51 | 52 | 53 | ;; ======================================== 54 | ;; Multimethods are good for data-driven behavior 55 | ;; ======================================== 56 | 57 | ;; Hiccup converts Clojure data structures to HTML 58 | (h/html [:input {:value "x"}]) 59 | (h/html [:textarea {:value "x"}]) 60 | 61 | (defmulti input (fn [input-type _] input-type)) 62 | 63 | (defmethod input :textarea 64 | [_ opts] 65 | [:textarea (dissoc opts :value) (:value opts)]) 66 | 67 | (defmethod input :select 68 | [_ {:keys [options] :as opts}] 69 | (let [selected-value (:value opts)] 70 | (into [:select (dissoc opts :options)] 71 | (mapv (fn [{:keys [name value]}] 72 | [:option {:value value 73 | :selected (= selected-value value)} name]) 74 | options)))) 75 | 76 | (defmethod input :default 77 | [input-type options] 78 | [:input (assoc options :type input-type)]) 79 | 80 | (input :select {:value "HI" 81 | :options [{:name "Alaska" :value "Ak"} 82 | {:name "Hawaii" :value "HI"}]}) 83 | 84 | (input :text {:value "abc"}) 85 | 86 | ;; Use data to produce a custom form 87 | (map (fn [form-field] 88 | (input (:type form-field) (dissoc form-field :type))) 89 | [{:name "Favorite Color" 90 | :type :textarea 91 | :value "blue"} 92 | {:name "Favorite State" 93 | :type :select 94 | :value "HI" 95 | :options [{:name "Alaska" :value "Ak"} 96 | {:name "Hawaii" :value "HI"}]}]) 97 | 98 | ;; You try: 99 | ;; * Create a method that handles radio buttons 100 | ;; * Create a method that handles check boxes 101 | ;; * Create a method that takes :date as its type, and produces 102 | ;; select dropdowns for month, day, and year 103 | 104 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex14_protocols_records.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex14-protocols-records 2 | (:require [cheshire.core :as json])) 3 | 4 | ;; ======================================== 5 | ;; Protocols 6 | ;; ======================================== 7 | 8 | ;; Protocols define abstractions, or collections of related 9 | ;; behavior. Clojure lets you extend existing types to implement these 10 | ;; abstractions. 11 | 12 | 13 | ;; We want to handle different data types, e.g. strings and maps, 14 | ;; differently. Protocols to the rescue! 15 | (defprotocol Serializable 16 | (serialize [this] "Converts this into a string, ready to send as an SQS message.")) 17 | 18 | (extend-protocol Serializable 19 | String (serialize [s] s) 20 | clojure.lang.PersistentHashMap (serialize [m] (json/generate-string m))) 21 | 22 | ;; Clojure's `ring` library lets you easily specify responses for HTTP 23 | ;; requests. Responses can be strings, files, and even other data 24 | ;; types. Another place where protocols are appropriate. 25 | ;; 26 | ;; The example below calculates an etag for use in HTTP response 27 | ;; headers. Browsers use etags to determine whether or note an asset 28 | ;; has expired. 29 | (defprotocol ExpirableContent 30 | (etag [x] "calculate etag")) 31 | 32 | (defn sha1 33 | [s] 34 | "actually do sha1") 35 | 36 | (extend-protocol ExpirableContent 37 | String 38 | (etag [s] (sha1 s)) 39 | 40 | java.io.File 41 | (etag [x] (str (.lastModified x) "-" (.length x))) 42 | 43 | ;; Default case 44 | java.lang.Object 45 | (etag [x] nil)) 46 | 47 | 48 | 49 | ;; ======================================== 50 | ;; Records 51 | ;; ======================================== 52 | ;; Records are custom, performant, map-like data types 53 | 54 | (defrecord WereWolf [name title]) 55 | 56 | ;; After defining a record type, there are three different ways to 57 | ;; create instances of that record 58 | (def lucian (WereWolf. "David" "London Tourism")) 59 | (def jacob (->WereWolf "Jacob" "Lead Shirt Discarder")) 60 | (def lucian (map->WereWolf {:name "Lucian" :title "CEO of Melodrama"})) 61 | 62 | ;; You can look up record values the same way you look up map values, 63 | ;; and you can also use Java field access interop: 64 | (.name lucian) 65 | (:name lucian) 66 | (get lucian :name) 67 | 68 | 69 | ;; When testing for equality, Clojure will check that all fields are 70 | ;; equal and that the two comparands have the same type: 71 | (= jacob (->WereWolf "Jacob" "Lead Shirt Discarder")) 72 | ; => true 73 | 74 | (= jacob (WereWolf. "David" "London Tourist")) 75 | ; => false 76 | 77 | (= jacob {:name "Jacob" :title "Lead Shirt Discarder"}) 78 | ; => false 79 | 80 | ;; Any function you can use on a map, you can also use on a record: 81 | (assoc jacob :title "Lead Third Wheel") 82 | 83 | ;; However, if you dissoc a field, the result’s type will be a plain 84 | ;; ol’ Clojure map; it won’t have the same data type as the original 85 | ;; record: 86 | (dissoc jacob :title) 87 | 88 | 89 | ;; You can extend records to implement protocols 90 | (defprotocol WereCreature 91 | (full-moon-behavior [this] "What the creature does on a full moon")) 92 | 93 | ;; extend-type is another way to implement protocols 94 | (extend-type WereWolf 95 | WereCreature 96 | (full-moon-behavior [this] (str name " will howl and murder"))) 97 | (full-moon-behavior jacob) 98 | ;; where did `name` come from? 99 | 100 | 101 | ;; You can also extend records when you define them 102 | (defrecord WereSimmons [name fabulosity] 103 | WereCreature 104 | (full-moon-behavior [this] 105 | (str name " will sweat to the oldies with a fabulosity rating of " fabulosity))) 106 | 107 | (def richard (WereSimmons. "Richard" 11)) 108 | (full-moon-behavior richard) 109 | 110 | 111 | ;; You try: 112 | ;; * create a protocol for formatting a Valeria card 113 | ;; * create two records, Citizen and Monster that implement that protocol 114 | ;; * read the files under projects/valeria/resources/citizens.edn and 115 | ;; monsters.edn. Convert those maps to records and format one citizen 116 | ;; and one monster. 117 | 118 | ;; The following code should work: 119 | (comment 120 | (format-card (map->Citizen {:name "Cleric" 121 | :base-cost 3 122 | :hit #{1} 123 | :you-payout [:magic 3] 124 | :other-payout [:magic 1] 125 | :role :holy}))) 126 | 127 | ;; Notes: 128 | ;; * You can reuse code from your previous Valeria project 129 | ;; * Theres an implementation under projects/valeria/src/valeria/cards.clj 130 | ;; but there's definitely room for improvement :) 131 | -------------------------------------------------------------------------------- /exercises/src/training/exercises/ex15_schema.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex15-schema 2 | (:require [schema.core :as s])) 3 | 4 | ;; ======================================== 5 | ;; Schema 6 | ;; ======================================== 7 | ;; Clojaure is a dynamically typed languaged with limited support for 8 | ;; type checking by default. Schema helps fill this role. It doesn't 9 | ;; do type checking per se, but it helps you describe the shape of 10 | ;; data that functions expect and it validates that shape. 11 | ;; 12 | ;; What's "the shape of data"? Simple example: 13 | 14 | (def Employee 15 | {:name s/Str 16 | :department s/Str 17 | :id s/Int 18 | :sanity s/Int}) 19 | 20 | 21 | (s/validate Employee {:name "Eagle Jones" 22 | :department "Fraud Prevention" 23 | :id 3 24 | :sanity 5}) 25 | 26 | ;; This throws an exception because it's missing a key 27 | (comment 28 | (s/validate Employee {:name "Henry Sock, Jr." 29 | :department "Fraud Prevention" 30 | :sanity 10})) 31 | 32 | ;; use s/check to get data describing the problem 33 | (s/check Employee {:name "Henry Sock, Jr." 34 | :department "Fraud Prevention" 35 | :sanity 10}) 36 | 37 | ;; You can nest validations 38 | (def Employee2 39 | {:name s/Str 40 | :department s/Str 41 | :address {:street1 s/Str 42 | (s/optional-key :street2) s/Str 43 | :city s/Str 44 | :zip s/Str 45 | :state (s/enum :nc :sc :va)} 46 | :sanity s/Int 47 | :id s/Int}) 48 | 49 | (def valid-employee 50 | {:name "Eagle Jones" 51 | :address {:street1 "123 Sycamore" 52 | :city "McLean" 53 | :zip "12345" 54 | :state :va} 55 | :sanity 5 56 | :department "Fraud Prevention" 57 | :id 3}) 58 | 59 | (def employee-invalid-address 60 | {:name "Eagle Jones" 61 | :department "Fraud Prevention" 62 | :address {:street1 "123 Sycamore"} 63 | :sanity 5 64 | :id 3}) 65 | 66 | (s/check Employee2 employee-invalid-address) 67 | 68 | (comment 69 | (s/validate Employee2 {:name "Eagle Jones" 70 | :department "Fraud Prevention" 71 | :sanity 5 72 | :id 3}) 73 | (s/validate Employee2 employee-invalid-address)) 74 | (s/validate Employee2 valid-employee) 75 | 76 | ;; You can compose validations because they're just data 77 | (def Address 78 | {:street1 s/Str 79 | (s/optional-key :street2) s/Str 80 | :city s/Str 81 | :zip s/Str 82 | :state (s/enum :nc :sc :va)}) 83 | 84 | (def Employee3 85 | {:name s/Str 86 | :department s/Str 87 | :address Address 88 | :sanity s/Int 89 | :id s/Int}) 90 | 91 | ;; Schema has macros that let you define functions and records with 92 | ;; schema validation 93 | ;; Notice the `s/` namespace prefix 94 | (s/defn give-raise 95 | [employee :- Employee3] 96 | :ok) 97 | 98 | (s/with-fn-validation 99 | (give-raise valid-employee)) 100 | 101 | (comment 102 | (s/with-fn-validation 103 | (give-raise employee-invalid-address))) 104 | 105 | ;; You try: 106 | ;; Create schemas for the Citizen and Monster Valeria cards. 107 | ;; The tricky bit will probably be the :you-payout and :other-payout. 108 | ;; Schema has great documentation: https://github.com/plumatic/schema 109 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s00_word_count.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s00-word-count 2 | (:require [clojure.string :as s])) 3 | 4 | (def test-string "Owwwww! My head feels like it's... like it's gonna have a baby.") 5 | 6 | ;; contains error because inc 7 | (defn word-count 8 | [s] 9 | (reduce (fn [counts word] (update counts (s/lower-case word) inc)) 10 | {} 11 | (s/split s #"[\. !]+"))) 12 | 13 | (defn word-count 14 | [s] 15 | (reduce (fn [counts word] (update counts (s/lower-case word) (fnil inc 0))) 16 | {} 17 | (s/split s #"[\. !]+"))) 18 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s01_total_word_count.clj: -------------------------------------------------------------------------------- 1 | (ns training.exercises.ex01-total-word-count 2 | (:require [clojure.string :as s])) 3 | 4 | (defn total-words 5 | [x] 6 | (count (s/split x #" "))) 7 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s03_banned_words.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s03-banned-words 2 | (:require [clojure.string :as str] 3 | [clojure.set :as set] 4 | [clojure.string :as s])) 5 | 6 | (defn contains-banned-words? 7 | [banned s] 8 | (not-empty (set/intersection banned (set (str/split s #" "))))) 9 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s04_function_basics.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s04-function-basics) 2 | 3 | (defn number-max 4 | [x y] 5 | (if (> x y) x y)) 6 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s05_odds.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s05-odds) 2 | 3 | (defn sums 4 | [d1 d2] 5 | (loop [d1' d1 6 | pairs []] 7 | (if (= d1' 0) 8 | pairs 9 | (recur (dec d1') 10 | (loop [d2' d2 11 | pairs' pairs] 12 | (if (= d2' 0) 13 | pairs' 14 | (recur (dec d2') 15 | (conj pairs' (+ d1' d2'))))))))) 16 | 17 | (defn odds 18 | [sum d1 d2] 19 | (let [sums (sums d1 d2)] 20 | (/ (count (filter #(= sum %) sums)) (* d1 d2)))) 21 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s07_core_functions.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s07-core-functions) 2 | 3 | (filter even? (map inc [0 1 2 3])) 4 | 5 | (reduce (fn [x y] (if (> x y) x y)) 6 | [0 1 2 3 4 5]) 7 | 8 | (reduce max [0 1 2 3 4 5]) 9 | 10 | (defn map' 11 | [f xs] 12 | (if (empty? xs) 13 | xs 14 | (cons (f (first xs)) 15 | (map' f (rest xs))))) 16 | 17 | (defn complement' 18 | [f] 19 | (fn [& args] (not (apply f args)))) 20 | 21 | (defn partial' 22 | [f & partial-args] 23 | (fn [& args] 24 | (apply f (concat partial-args args)))) 25 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s08_movie_time.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s08-movie-time 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as str])) 4 | 5 | (defn get-movies 6 | [] 7 | (->> (io/resource "ex08-movie-time/ratings.list") 8 | slurp 9 | str/split-lines 10 | (map #(str/split % #"\s{2,}")) 11 | (map (fn [[_ _ votes rating title]] 12 | {:votes (Integer. votes) 13 | :rating (Double. rating) 14 | :title (str/replace title #"\"" "")})))) 15 | 16 | (def movies (get-movies)) 17 | 18 | (defn max-by 19 | [f x y] 20 | (if (> (f x) (f y)) x y)) 21 | 22 | (defn highest-rated 23 | [] 24 | (reduce (fn [x y] 25 | (let [rx (:rating x) 26 | ry (:rating y)] 27 | (if (= rx ry) 28 | (max-by :votes x y) 29 | (max-by :rating x y)))) 30 | (filter #(> (:votes %) 1000) movies))) 31 | 32 | (defn select-movies 33 | [min-rating max-rating min-votes] 34 | (sort-by :rating 35 | (filter (fn [{:keys [rating votes]}] 36 | (and (> rating min-rating) 37 | (< rating max-rating) 38 | (> votes min-votes))) 39 | movies))) 40 | 41 | (defn vote-average 42 | [] 43 | (let [totals (reduce (fn [{:keys [total-votes ratings]} {:keys [votes rating]}] 44 | {:total-votes (+ total-votes votes) 45 | :ratings (+ ratings (* votes rating))}) 46 | {:total-votes 0 47 | :ratings 0} 48 | movies)] 49 | (/ (:ratings totals) (:total-votes totals)))) 50 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/s12_java_interop.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.s12-java-interop) 2 | 3 | (.indexOf "some string" "str") 4 | -------------------------------------------------------------------------------- /exercises/src/training/solutions/sch01_find_the_gap.clj: -------------------------------------------------------------------------------- 1 | (ns training.solutions.sch1-find-the-gap) 2 | 3 | ;; Find the smallest natural number not in a given finite set of 4 | ;; natural numbers. Suppose the numbers are unsorted: 5 | 6 | (def numbers [8 23 9 0 12 11 1 10 13 7 41 4 14 21 5 17 3 19 2 6]) 7 | 8 | ;; How can you find the number in linear time? You can't sort because 9 | ;; sorting can't be done in linear time. 10 | 11 | (defn minfrom 12 | [a [len xs]] 13 | (let [b (+ a 1 (int (/ len 2))) 14 | [us vs] (partition-by #(< % b) xs) 15 | m (count us)] 16 | (cond (zero? len) a 17 | (= m (- b a)) (recur b [(- len m) vs]) 18 | :else (recur a [m us])))) 19 | 20 | (defn minfree 21 | [xs] 22 | (minfrom 0 [(count xs) xs])) 23 | -------------------------------------------------------------------------------- /projects/blackjack/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /projects/blackjack/README.md: -------------------------------------------------------------------------------- 1 | # blackjack 2 | 3 | Create a command-line blackjack game. 4 | 5 | http://www.bicyclecards.com/how-to-play/blackjack/ has the rules to 6 | blackjack, in case you find that helpful. Ignore the parts about 7 | splitting pairs, doubling down, and insurance. 8 | 9 | ## Gameplay 10 | 11 | Do `lein run` to try out a working version of the game. Below I 12 | describe the gameplay in detail. 13 | 14 | When the application starts, the player has $1,000. The player is 15 | presented with a prompt to place a bet, like this: 16 | 17 | ``` 18 | You have 1000 money 19 | Please place a bet by entering a number: 20 | ``` 21 | 22 | After the player places a bet, the game should deal to him and to the 23 | "dealer". 24 | 25 | 1. Deal a card to the player 26 | 2. Deal a card to the dealer 27 | 3. Deal a card to the player 28 | 4. Deal a card to the dealer 29 | 30 | You don't have to give any progress indicators for dealing, just show 31 | what was dealt, like this: 32 | 33 | ``` 34 | ----- 35 | Your hand: ♣5, ♥3 (8) 36 | Dealer hand: ♥10 37 | Bet: 100 38 | ----- 39 | 40 | (h)it or (s)tand? 41 | ``` 42 | 43 | Notice a couple things: 44 | 45 | * Your hand's total is displayed 46 | * One of the dealer's cards is hidden, like in real blackjack 47 | * The dealer's total is not displayed 48 | 49 | At this point you can `(h)it` or `(s)tand`. `(s)tand` means pass play 50 | to the dealer (detailed below). 51 | 52 | If the user selects `h` for hit, deal her a card. Check that the user 53 | hasn't busted (gone over 21). 54 | 55 | Note that Aces can count as either a 1 or 11. When calculating the 56 | point value for a hand, take this into account: 57 | 58 | * At first, count Aces as 11's 59 | * If the player's hand is a bust, change one Ace from 11 to 1 60 | * If the player's hand is still a bust, keep changing existing Aces 61 | from 11 to 1 62 | 63 | If the player busts, the round is over and the player loses the money. 64 | 65 | If the player doesn't bust, play passes to the dealer: 66 | 67 | * If the dealer's total is 17 or more, he must stand 68 | * If the total is 16 or under, dealer must take a card 69 | * If the dealer has an ace and counting it as an 11 would bring his 70 | total to 17 or above, he must count the ace as 11 and stand 71 | 72 | Now the bet is settled. If the player has a higher score and didn't 73 | bust, or if the dealer busts, the player wins back 2x his bet. So if 74 | he bet $100, he gets back the original $100 and an additonal $100. If 75 | there's a tie, the player gets back his money. If the dealer wins, the 76 | player loses his money. 77 | 78 | ## Naturals 79 | 80 | If the player is dealt 21 - an ace and a ten card (including 10, jack, 81 | queen, king) - then he has gotten a natural. If the dealer does not 82 | have a natural as well, then the player gets 2.5x the bet; if he bet 83 | $100, he gets that $100 back plus $150. 84 | 85 | If the player and dealer both have naturals, it's a tie and the player 86 | gets her money back. 87 | 88 | If the dealer gets a natural and the player doesn't, the dealer wins. 89 | 90 | ## Hints 91 | 92 | Try to break this down into smaller parts as much as possible, and 93 | work on the smaller parts one at a time. 94 | 95 | If it's too much, try going through the provided solution and 96 | re-writing it in your own project. This is a great way to learn. 97 | -------------------------------------------------------------------------------- /projects/blackjack/project.clj: -------------------------------------------------------------------------------- 1 | (defproject blackjack "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]] 7 | :main ^:skip-aot blackjack.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /projects/blackjack/src/blackjack/core.clj: -------------------------------------------------------------------------------- 1 | (ns blackjack.core 2 | (:gen-class) 3 | (:require [clojure.string :as str] 4 | [clojure.set :as set])) 5 | 6 | ;; Arbitrary amount of starting money 7 | (def starting-money 1000) 8 | 9 | ;; Use nested loops to iterate over suits and ranks to generate a deck 10 | (def deck 11 | "A deck of cards" 12 | (loop [suits ["♠" "♥" "♦" "♣"] 13 | deck #{}] 14 | (if (empty? suits) 15 | deck 16 | (let [[suit & remaining-suits] suits] 17 | (recur remaining-suits 18 | (loop [ranks ["A" 2 3 4 5 6 7 8 9 10 "J" "Q" "K"] 19 | deck' deck] 20 | (if (empty? ranks) 21 | deck' 22 | (let [[rank & remaining-ranks] ranks] 23 | (recur remaining-ranks 24 | (conj deck' [suit rank])))))))))) 25 | 26 | (def initial-game 27 | "The starting state of the game" 28 | {:player {:money starting-money 29 | :bet 0 30 | :hand []} 31 | :dealer {:hand []} 32 | :deck deck 33 | :outcome nil}) 34 | 35 | ;; This is used when calculating the value of a hand 36 | (def ten-cards 37 | "A card value in this set is worth 10 points" 38 | #{10 "J" "Q" "K"}) 39 | 40 | 41 | (defn get-input 42 | "Waits for user to enter text and hit enter, then cleans the input. 43 | If the user doesn't enter anything, it prompts again." 44 | [] 45 | (let [input (str/trim (read-line))] 46 | (if (empty? input) 47 | (do (println "Please enter some input") 48 | (recur)) 49 | (str/lower-case input)))) 50 | 51 | ;;===== 52 | ;; Bets 53 | ;;===== 54 | ;; Notice that we separate the impure function, prompt-bet, from the 55 | ;; pure function, place-bet. In functional programming languages, we 56 | ;; try to write pure functions as much as possible, and reduce the 57 | ;; footprint of impure functions. 58 | 59 | (defn place-bet 60 | "Transfer from player's money pile to bet pile" 61 | [game bet] 62 | (-> game 63 | (update-in [:player :money] - bet) 64 | (update-in [:player :bet] + bet))) 65 | 66 | (defn prompt-bet 67 | "Prompts user for a bet, then updates game state" 68 | [game] 69 | (println "Please place a bet by entering a number:") 70 | (place-bet game (Integer. (get-input)))) 71 | 72 | ;;===== 73 | ;; Dealing 74 | ;;===== 75 | (defn random-card 76 | "select a random card from the deck" 77 | [deck] 78 | (get (vec deck) (rand-int (count deck)))) 79 | 80 | (defn deal-to 81 | "Remove a card from the deck and give it to someone" 82 | [card-receiver-path {:keys [deck] :as game}] 83 | (let [card (random-card deck)] 84 | (-> game 85 | (update-in card-receiver-path conj card) 86 | (update :deck disj card)))) 87 | 88 | (def deal-player (partial deal-to [:player :hand])) 89 | (def deal-dealer (partial deal-to [:dealer :hand])) 90 | 91 | (defn deal 92 | [game] 93 | (-> game 94 | deal-player 95 | deal-dealer 96 | deal-player 97 | deal-dealer)) 98 | 99 | (defn player-hand 100 | [game] 101 | (get-in game [:player :hand])) 102 | 103 | (defn dealer-hand 104 | [game] 105 | (get-in game [:dealer :hand])) 106 | 107 | 108 | ;;===== 109 | ;; Hand outcomes 110 | ;;===== 111 | (defn outcome 112 | "Assign an outcome" 113 | [game x] 114 | (assoc game :outcome x)) 115 | 116 | ;;===== 117 | ;; Handling naturals 118 | ;;===== 119 | ;; A natural is when the player or dealer has an ace and a 10-card 120 | 121 | (defn natural-hand? 122 | "Does a hand contain an ace and a 10. Example hand: 123 | [[\"♠\" 3] [\"♠\" 10]]" 124 | [hand] 125 | ;; Remember that you can use sets as functions. When you apply a set 126 | ;; to a value, it returns that value if it contains that 127 | ;; value. Otherwise it returns nil. 128 | (let [ranks (set (map second hand))] 129 | (and (ranks "A") 130 | (not-empty (set/intersection ranks ten-cards))))) 131 | 132 | (defn handle-naturals 133 | "Assign outcome based on whether both have naturals, just one, or 134 | neither" 135 | [game] 136 | (let [player-natural? (natural-hand? (player-hand game)) 137 | dealer-natural? (natural-hand? (dealer-hand game))] 138 | (cond (and player-natural? dealer-natural?) (outcome game :tie) 139 | player-natural? (outcome game :player-won-natural) 140 | dealer-natural? (outcome game :player-lost) 141 | :else game))) 142 | 143 | ;;===== 144 | ;; Check for busts 145 | ;;===== 146 | (defn numberify-card-rank 147 | "Convert non-numeric cards to a number" 148 | [rank] 149 | (cond (ten-cards rank) 10 150 | (= rank "A") 11 151 | :else rank)) 152 | 153 | (defn best-hand-score 154 | "Gets as close as possible to 21 without going over" 155 | [hand] 156 | (let [ranks (map (comp numberify-card-rank second) hand) 157 | initial-sum (reduce + ranks)] 158 | (loop [sum initial-sum 159 | ranks ranks] 160 | ;; This bit checks if you've busted because you're treating an 161 | ;; ace as 11. If so, get the score with one ace converted to a 1. 162 | (if (and (> sum 21) (contains? (set ranks) 11)) 163 | (recur (- sum 10) (conj (->> ranks sort reverse rest) 1)) 164 | sum)))) 165 | 166 | (defn busted? 167 | [hand] 168 | (> (best-hand-score hand) 21)) 169 | 170 | (defn check-player-bust 171 | [game] 172 | (if (busted? (player-hand game)) 173 | (outcome game :player-busted) 174 | game)) 175 | 176 | (defn check-dealer-bust 177 | [game] 178 | (if (busted? (dealer-hand game)) 179 | (outcome game :dealer-busted) 180 | game)) 181 | 182 | ;;===== 183 | ;; Displaying game 184 | ;;===== 185 | ;; Notice that all the format functions return strings and don't do 186 | ;; any printing. This is part of the general approach of separting out 187 | ;; impure functions as much as possible. 188 | 189 | (defn format-hand 190 | [hand] 191 | (->> hand 192 | (map #(str/join "" %)) 193 | (str/join ", "))) 194 | 195 | (defn format-dealer-hand 196 | "Special rules for the dealer hand - you don't want to reveal his 197 | hidden card or his score until after the player plays" 198 | [hand reveal-full] 199 | (if reveal-full 200 | (str (format-hand hand) " (" (best-hand-score hand) ")") 201 | (format-hand (rest hand)))) 202 | 203 | (defn format-game 204 | [game reveal-dealer] 205 | (let [ph (player-hand game)] 206 | (str "-----\n" 207 | "Your hand: " (format-hand ph) " (" (best-hand-score ph) ")\n" 208 | "Dealer hand: " (format-dealer-hand (dealer-hand game) reveal-dealer) "\n" 209 | "Bet: " (get-in game [:player :bet]) "\n" 210 | "-----\n"))) 211 | 212 | (defn print-game 213 | [game & [reveal-dealer]] 214 | (-> game 215 | (format-game reveal-dealer) 216 | println)) 217 | 218 | 219 | ;;===== 220 | ;; Play additional cards 221 | ;;===== 222 | (defn player-play 223 | "Prompt player to hit or stand, check for player bust" 224 | [game] 225 | ;; Check that there's no outcome from the natural check 226 | (when (not (:outcome game)) 227 | (loop [game game] 228 | (print-game game) 229 | (if (:outcome game) 230 | game 231 | (do (println "(h)it or (s)tand?") 232 | (let [response (get-input)] 233 | (if (= "h" response) 234 | (recur (check-player-bust (deal-player game))) 235 | (check-player-bust game)))))))) 236 | 237 | (defn dealer-play 238 | "Keep giving the dealer cards until his hand exceeds 17" 239 | [game] 240 | (if (not (:outcome game)) 241 | (loop [game game] 242 | (if (>= (best-hand-score (dealer-hand game)) 17) 243 | (check-dealer-bust game) 244 | (recur (deal-dealer game)))) 245 | game)) 246 | 247 | ;;===== 248 | ;; Final outcome 249 | ;;===== 250 | (defn determine-outcome 251 | "If the outcome wasn't already settled with naturals, compare scores" 252 | [game] 253 | (let [dealer-score (best-hand-score (dealer-hand game)) 254 | player-score (best-hand-score (player-hand game))] 255 | (cond (:outcome game) game 256 | (> dealer-score player-score) (outcome game :player-lost) 257 | (> player-score dealer-score) (outcome game :player-won) 258 | :else (outcome game :tie)))) 259 | 260 | (def outcomes 261 | {:player-busted "You busted! You lost!" 262 | :dealer-busted "The dealer busted! You won!" 263 | :tie "It was a tie!" 264 | :player-won-natural "You won a natural!" 265 | :player-lost "You lost!" 266 | :player-won "You won!"}) 267 | 268 | (defn print-outcome 269 | [game] 270 | (print-game game true) 271 | (println ((:outcome game) outcomes)) 272 | game) 273 | 274 | (defn print-money 275 | [game] 276 | (println "You have " (get-in game [:player :money]) " money") 277 | game) 278 | 279 | (defn pay-player 280 | [game multiplier] 281 | (update-in game [:player :money] + (* multiplier (get-in game [:player :bet])))) 282 | 283 | (defn tie 284 | "Return the player's bet" 285 | [game] 286 | (pay-player game 1)) 287 | 288 | (defn player-won-natural 289 | "Return the player's bet, plus 1.5x the bet" 290 | [game] 291 | (pay-player game 2.5)) 292 | 293 | (defn player-won 294 | "Return the player's 2x the player's bet" 295 | [game] 296 | (pay-player game 2)) 297 | 298 | (defn settle-outcome 299 | [{:keys [outcome] :as game}] 300 | (cond (#{:player-lost :player-busted} outcome) game 301 | (#{:player-won :dealer-busted} outcome) (player-won game) 302 | (= outcome :player-won-natural) (player-won-natural game) 303 | (= outcome :tie) (tie game))) 304 | 305 | (defn round 306 | [game] 307 | (-> game 308 | prompt-bet 309 | deal 310 | handle-naturals 311 | player-play 312 | dealer-play 313 | determine-outcome 314 | print-outcome 315 | settle-outcome 316 | print-money)) 317 | 318 | (defn game-loop 319 | "Wraps game rounds, prompting to continue after each round and 320 | resetting state such that players have no cards, the deck is full, 321 | and the player's money is retained" 322 | [game] 323 | (print-money game) 324 | (loop [game game] 325 | (let [game-result (round game)] 326 | (println "Continue? y/n") 327 | (if (= (get-input) "y") 328 | (recur (assoc-in game [:player :money] (get-in game-result [:player :money]))) 329 | (do (println "Bye!") 330 | (System/exit 0)))))) 331 | 332 | (defn -main 333 | [] 334 | (game-loop initial-game)) 335 | -------------------------------------------------------------------------------- /projects/character-creator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /projects/character-creator/README.md: -------------------------------------------------------------------------------- 1 | # character-creator 2 | 3 | This small project demonstrates how you can update and modify values 4 | using immutable data structures and functional programming. 5 | 6 | Use `lein run` to start it. 7 | -------------------------------------------------------------------------------- /projects/character-creator/project.clj: -------------------------------------------------------------------------------- 1 | (defproject character-creator "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]] 7 | :main ^:skip-aot character-creator.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /projects/character-creator/src/character_creator/core.clj: -------------------------------------------------------------------------------- 1 | (ns character-creator.core 2 | (:gen-class)) 3 | 4 | (def initial-state 5 | {:name "Smooches McCutes" 6 | :attributes {:strength 10 7 | :intelligence 10 8 | :charisma 10 9 | :dexterity 10} 10 | :attribute-points 5}) 11 | 12 | (defn format-character 13 | "Given a map representing a character, return a string that 14 | describes it in a way that's easy for humans to read" 15 | [{:keys [name attributes attribute-points]}] 16 | (str "Name: " name 17 | "Attributes \n" 18 | "STR: " (:strength attributes) "\n" 19 | "INT: " (:intelligence attributes) "\n" 20 | "CHR: " (:charisma attributes) "\n" 21 | "DEX: " (:dexterity attributes) "\n" 22 | "Remaining attribute points: " attribute-points)) 23 | 24 | (defn assign-attribute-point 25 | "Updates the character map, adding one to the chosen attribute and 26 | deducting one from remaining attribute points" 27 | [character attr] 28 | (-> character 29 | (update-in [:attributes attr] inc) 30 | (update :attribute-points dec))) 31 | 32 | ;;; 33 | ;; Handle input 34 | ;;; 35 | 36 | (def input-map 37 | "Maps the user's input, a string, to the corresponding attribute key" 38 | {"s" :strength 39 | "i" :intelligence 40 | "c" :charisma 41 | "d" :dexterity}) 42 | 43 | (defn get-input 44 | "Waits for user to enter text and hit enter, then cleans the input" 45 | ([] (get-input "")) 46 | ([default] 47 | (let [input (clojure.string/trim (read-line))] 48 | (if (empty? input) 49 | default 50 | (clojure.string/lower-case input))))) 51 | 52 | (defn prompt-attribute-point 53 | "Prints a prompt and adds an attribute point based on input." 54 | [character] 55 | (println (format-character character)) 56 | (println "Assign an attribute point to (s)tr, (i)nt, (c)hr, or (d)ex:") 57 | (let [choice (get-input) 58 | attr (get input-map choice)] 59 | (if attr 60 | (assign-attribute-point character attr) 61 | (do (println "I don't recognize that choice") 62 | (recur character))))) 63 | 64 | ;; Notice that this uses recur to continue prompting for point 65 | ;; assignment. We're updating the character, building up a final 66 | ;; value, using recursion. 67 | (defn builder-loop 68 | "Continue building a character until there are no more attribute points" 69 | [character] 70 | (if (zero? (:attribute-points character)) 71 | (do (println (str "\n\nYour final character:\n" (format-character character))) 72 | (System/exit 0)) 73 | (recur (prompt-attribute-point character)))) 74 | 75 | (defn -main 76 | [& args] 77 | (println "Welcome to your character builder!") 78 | (builder-loop initial-state)) 79 | -------------------------------------------------------------------------------- /projects/hn/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /projects/hn/README.md: -------------------------------------------------------------------------------- 1 | # hn 2 | 3 | The purpose of this project is get experience interacting with a Java 4 | library from a Clojure project. You'll do this by using Selenium to 5 | visit Hacker News (http://news.ycombinator.com) and scrape link 6 | titles. 7 | 8 | [All the Selenium commands and operations](http://www.seleniumhq.org/docs/03_webdriver.jsp#introducing-the-selenium-webdriver-api-by-example) 9 | has everything you'll need. 10 | 11 | You'll need to install Firefox. 12 | 13 | For extra credit, visit each link and get the name of every 14 | commentor. Place the result in a vector of maps, like this: 15 | 16 | ```clojure 17 | [{:title "Anguish: Invisible Programming Language and Invisible Data Theft" 18 | :commentors #{"danra" "yokohummer7" "lolc"}}] 19 | ``` 20 | 21 | Save this to the file "hn-scrape.edn". 22 | -------------------------------------------------------------------------------- /projects/hn/project.clj: -------------------------------------------------------------------------------- 1 | (defproject hn "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.seleniumhq.selenium/selenium-java "2.53.0"] 8 | [org.seleniumhq.selenium/htmlunit-driver "2.21"]] 9 | :main ^:skip-aot hn.core 10 | :target-path "target/%s" 11 | :profiles {:uberjar {:aot :all}}) 12 | -------------------------------------------------------------------------------- /projects/hn/src/hn/core.clj: -------------------------------------------------------------------------------- 1 | (ns hn.core 2 | (:import [org.openqa.selenium By WebDriver WebElement] 3 | [org.openqa.selenium.firefox FirefoxDriver]) 4 | (:gen-class)) 5 | 6 | (defn scrape 7 | [] 8 | (let [driver (doto (FirefoxDriver.) 9 | (.get "http://news.ycombinator.com"))] 10 | 11 | 12 | (let [titles (->> (.findElements driver (By/cssSelector ".title a:nth-child(2)")) 13 | (map #(.getText %)))] 14 | (doseq [title titles] 15 | (println title))) 16 | 17 | ;; closes the browser 18 | (.quit driver))) 19 | 20 | (defn -main 21 | [& args] 22 | (scrape)) 23 | -------------------------------------------------------------------------------- /projects/mic-check/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /projects/mic-check/README.md: -------------------------------------------------------------------------------- 1 | # mic-check 2 | 3 | Check that you're comfortable with the basic of building and running a 4 | Clojure app, and using the REPL. Try these: 5 | 6 | * Run `lein run` in the command line. This should print `Testing... testing...` 7 | * Run `lein uberjar`, then `java -jar target/uberjar/mic-check-0.1.0-SNAPSHOT-standalone.jar`. This should print `Testing... testing...` 8 | * In your editor, navigate to `core.clj` and start your REPL. Make sure the REPL is in the `mic-check.core` namespace. Change `core.clj` so that the `-main` function will print `Is this thing on?`. Then compile the file. In the REPL, type `(-main)` and hit enter. This should print `Is this thing on?`. 9 | -------------------------------------------------------------------------------- /projects/mic-check/project.clj: -------------------------------------------------------------------------------- 1 | (defproject mic-check "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]] 7 | :main ^:skip-aot mic-check.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /projects/mic-check/src/mic_check/core.clj: -------------------------------------------------------------------------------- 1 | (ns mic-check.core 2 | (:gen-class)) 3 | 4 | (defn -main 5 | [& args] 6 | (println "Testing... testing...")) 7 | -------------------------------------------------------------------------------- /projects/phrasebook/PirateConversation.java: -------------------------------------------------------------------------------- 1 | import pirate_phrases.*; 2 | 3 | public class PirateConversation 4 | { 5 | public static void main(String[] args) 6 | { 7 | Greetings greetings = new Greetings(); 8 | greetings.hello(); 9 | 10 | Farewells farewells = new Farewells(); 11 | farewells.goodbye(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/phrasebook/PiratePhrases.java: -------------------------------------------------------------------------------- 1 | public class PiratePhrases 2 | { 3 | public static void main(String[] args) 4 | { 5 | System.out.println("Shiver me timbers!!!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/phrasebook/pirate_phrases/Farewells.java: -------------------------------------------------------------------------------- 1 | package pirate_phrases; 2 | 3 | public class Farewells 4 | { 5 | public static void goodbye() 6 | { 7 | System.out.println("A fair turn of the tide ter ye thar, ye magnificent sea friend!!"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/phrasebook/pirate_phrases/Greetings.java: -------------------------------------------------------------------------------- 1 | package pirate_phrases; 2 | 3 | public class Greetings 4 | { 5 | public static void hello() 6 | { 7 | System.out.println("Shiver me timbers!!!"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/valeria/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /projects/valeria/README.md: -------------------------------------------------------------------------------- 1 | # Valeria 2 | 3 | Part of the strategy of Valeria is building an effective 4 | "tableau". This application will help you explore different tableau 5 | possibilities. 6 | 7 | Create a command-line Clojure application that does the following: 8 | 9 | * Lists all Citizen cards 10 | * Lets you buy a card 11 | * Lets you return a card 12 | * Lets you view a card 13 | 14 | Remember that `lein run` will run an application. 15 | 16 | ## Listing all Citizen cards 17 | 18 | When the application first loads, the user should see a numbered list 19 | of Citizen cards. Each list entry should include: 20 | 21 | * An index 22 | * The Citizen's name 23 | * The base cost 24 | * The number you've bought, in parentheses 25 | 26 | Like this: 27 | 28 | ``` 29 | 0. Cleric 3 (0) 30 | 1. Merchant 2 (1) 31 | 2. Mercenary 3 (0) 32 | 3. Archer 4 (0) 33 | 4. Peasant 2 (0) 34 | 5. Knight 2 (0) 35 | 6. Rogue 2 (0) 36 | 7. Champion 2 (0) 37 | 8. Paladin 2 (0) 38 | 9. Butcher 1 (0) 39 | Total cost: 2 40 | (b)uy, (r)eturn, or (v)iew a card, or (reset). Ex: "b 0" to buy cleric 41 | ``` 42 | 43 | As you can see, this dashboard also includes the total cost every card 44 | you've bought so far, and a list of commands. 45 | 46 | ## Buying cards 47 | 48 | The command line application should let you type `b 0` to, for 49 | example, buy a cleric. This should increment the count of clerics 50 | you've bought. When you buy another cleric, its cost should go up, 51 | reflecting the game's rules. 52 | 53 | For example, the cleric's base cost is 3. You have to pay an 54 | additional gold for every cleric you own, so if you own two clerics 55 | it will cost 5 gold to buy a third. 56 | 57 | ## Returning cards 58 | 59 | Let the user type `r 0` to return a cleric. Don't let the owned count 60 | get into negative numbers; you should never own -1 clerics. 61 | 62 | ## View a card 63 | 64 | Type `v 0` to view all the details for the cleric. Here are the 65 | cleric's details: 66 | 67 | ``` 68 | ======== 69 | Name: Cleric 70 | Base cost: 3 71 | Hits on: 1 72 | You payout: 3 magic 73 | Other payout: 1 magic 74 | Odds: 11/36 75 | Role: holy 76 | ======== 77 | ``` 78 | 79 | Some cards, like the merchant, have more complex payout 80 | options. Here's how the merchant should display: 81 | 82 | ``` 83 | ======== 84 | Name: Merchant 85 | Base cost: 2 86 | Hits on: 2 87 | You payout: 2 gold || 2 magic 88 | Other payout: 1 gold 89 | Odds: 1/3 90 | Role: worker 91 | ======== 92 | ``` 93 | 94 | Its payout is 2 gold OR 2 magic, and that's shown as `2 gold || 2 95 | magic`. 96 | 97 | 98 | ## Extra credit 99 | 100 | When you display the dashboard, display the per-turn value of all your 101 | cards based on all cards you own and their probabilities of hitting. 102 | 103 | (note: I don't actually know how to do this, but it sounds fun!) 104 | -------------------------------------------------------------------------------- /projects/valeria/project.clj: -------------------------------------------------------------------------------- 1 | (defproject valeria "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]] 7 | :main ^:skip-aot valeria.core 8 | :target-path "target/%s" 9 | :profiles {:uberjar {:aot :all}}) 10 | -------------------------------------------------------------------------------- /projects/valeria/resources/citizens.edn: -------------------------------------------------------------------------------- 1 | [{:name "Cleric" 2 | :base-cost 3 3 | :hit #{1} 4 | :you-payout [:magic 3] 5 | :other-payout [:magic 1] 6 | :role :holy} 7 | {:name "Merchant" 8 | :base-cost 2 9 | :hit #{2} 10 | :you-payout [:or [:gold 2] [:magic 2]] 11 | :other-payout [:gold 1] 12 | :role :worker} 13 | {:name "Mercenary" 14 | :base-cost 3 15 | :hit #{3} 16 | :you-payout [:and [:gold 1] [:strength 1]] 17 | :other-payout [:convert [:strength 1] [:gold 2]] 18 | :role :shadow} 19 | {:name "Archer" 20 | :base-cost 4 21 | :hit #{4} 22 | :you-payout [:strength 2] 23 | :other-payout [:strength 1] 24 | :role :soldier} 25 | {:name "Peasant" 26 | :base-cost 2 27 | :hit #{5} 28 | :you-payout [:gold 1] 29 | :other-payout [:gold 1] 30 | :role :worker} 31 | {:name "Knight" 32 | :base-cost 2 33 | :hit #{6} 34 | :you-payout [:strength 1] 35 | :other-payout [:strength 1] 36 | :role :soldier} 37 | {:name "Rogue" 38 | :base-cost 2 39 | :hit #{7} 40 | :you-payout [:and [:strength 2] [:gold 2]] 41 | :other-payout [:and [:strength 1] [:gold 1]] 42 | :role :shadow} 43 | {:name "Champion" 44 | :base-cost 2 45 | :hit #{8} 46 | :you-payout [:strength 4] 47 | :other-payout [:convert [:gold 1] [:strength 4]] 48 | :role :soldier} 49 | {:name "Paladin" 50 | :base-cost 2 51 | :hit #{9 10} 52 | :you-payout [:and [:strength 1] [:magic 2]] 53 | :other-payout [:convert [:strength 1] [:magic 3]] 54 | :role :holy} 55 | {:name "Butcher" 56 | :base-cost 1 57 | :hit #{11 12} 58 | :you-payout [:* [:gold 2] :citizen] 59 | :other-payout [:gold 4] 60 | :role :worker}] 61 | -------------------------------------------------------------------------------- /projects/valeria/resources/monsters.edn: -------------------------------------------------------------------------------- 1 | [{:name "Goblin" 2 | :rewards [:gold 1] 3 | :hitpoints 1 4 | :victory-points 1 5 | :location "Hills"} 6 | {:name "Goblin Mage" 7 | :rewards [:or [:gold 1] [:magic 1]] 8 | :hitpoints 3 9 | :victory-points 2 10 | :location "Hills"} 11 | {:name "Goblin King" 12 | :rewards [:* [:gold 1] :hill-monster] 13 | :hitpoints 6 14 | :victory-points 4 15 | :location "Hills"}] 16 | -------------------------------------------------------------------------------- /projects/valeria/src/valeria/cards.clj: -------------------------------------------------------------------------------- 1 | (ns valeria.cards 2 | (:require [valeria.core :as core] 3 | [clojure.string :as str] 4 | [clojure.java.io :as io])) 5 | 6 | (def citizens (-> "citizens.edn" io/resource slurp edn/read-string)) 7 | (def monsters (-> "monsters.edn" io/resource slurp edn/read-string)) 8 | 9 | (defprotocol Card 10 | (format [x] "Produce a string meant for human eyes describing the card")) 11 | 12 | (defrecord Citizen [name base-cost hit you-payout other-payout role] 13 | Card 14 | (format [this] 15 | (str "Name: " name "\n" 16 | "Base cost: " base-cost "\n" 17 | "Hits on: " (str/join ", " (sort hit)) "\n" 18 | "You payout: " (core/format-payout you-payout) "\n" 19 | "Other payout: " (core/format-payout other-payout) "\n" 20 | "Odds: " (core/card-odds this) "\n" 21 | "Role: " (name role)))) 22 | -------------------------------------------------------------------------------- /projects/valeria/src/valeria/core.clj: -------------------------------------------------------------------------------- 1 | (ns valeria.core 2 | (:require [clojure.string :as str] 3 | [clojure.java.io :as io] 4 | [clojure.edn :as edn] 5 | [valeria.odds :as odds]) 6 | (:gen-class)) 7 | 8 | (def cards (-> "citizens.edn" io/resource slurp edn/read-string)) 9 | 10 | ;;===== 11 | ;; Odds of hitting card 12 | ;;===== 13 | 14 | 15 | (defn hit-odds 16 | "If the hit is greater than 6, then it can only be hit via the sum 17 | of both dice and normal sum odds apply. 18 | 19 | If the hit is 6 or less, then it a single die can also hit. The 20 | probability of that is 11/36." 21 | [hit] 22 | (let [base-odds (odds/sum-odds hit 6 6)] 23 | (if (<= hit 6) 24 | (+ base-odds 11/36) 25 | base-odds))) 26 | 27 | (defn card-odds 28 | "Add up the odds for every possible hit. For example, Butcher hits 29 | on 11 and 12, so add the odds of an 11 with the odds of a 12." 30 | [{:keys [hit]}] 31 | (reduce + (map hit-odds hit))) 32 | 33 | ;;===== 34 | ;; Format card details 35 | ;;===== 36 | (defn format-payout 37 | "String representation of a payout" 38 | [payout] 39 | (let [type (first payout)] 40 | (cond (= type :or) 41 | (str/join " || " (map format-payout (rest payout))) 42 | 43 | (= type :and) 44 | (str/join " && " (map format-payout (rest payout))) 45 | 46 | (= type :convert) 47 | (str "convert " (format-payout (second payout)) " to " (format-payout (last payout))) 48 | 49 | (= type :*) 50 | (str (format-payout (second payout)) " per " (name (last payout))) 51 | 52 | :else 53 | (str (second payout) " " (name type))))) 54 | 55 | ;; TODO payout odds 56 | (defn format-card 57 | [card] 58 | (str "Name: " (:name card) "\n" 59 | "Base cost: " (:base-cost card) "\n" 60 | "Hits on: " (str/join ", " (sort (:hit card))) "\n" 61 | "You payout: " (format-payout (:you-payout card)) "\n" 62 | "Other payout: " (format-payout (:other-payout card)) "\n" 63 | "Odds: " (card-odds card) "\n" 64 | "Role: " (name (:role card)))) 65 | 66 | ;;===== 67 | ;; Format list of cards 68 | ;;===== 69 | (defn format-card-row 70 | [bought index card] 71 | (str index ". " (:name card) 72 | " " (:base-cost card) 73 | "\t\t(" (get bought index 0) ")")) 74 | 75 | (defn format-card-table 76 | [bought] 77 | (->> cards 78 | (map-indexed (partial format-card-row bought)) 79 | (str/join "\n"))) 80 | 81 | ;;===== 82 | ;; Total card cost 83 | ;;===== 84 | (defn cost 85 | [{:keys [base-cost]} n] 86 | (if n 87 | (reduce + (range base-cost (+ base-cost n))) 88 | 0)) 89 | 90 | (defn total-cost 91 | [cards bought] 92 | (->> cards 93 | (map-indexed (fn [index card] (cost card (get bought index)))) 94 | (reduce +))) 95 | 96 | ;;===== 97 | ;; Interact with dashboard 98 | ;;===== 99 | (defn get-input 100 | "Waits for user to enter text and hit enter, then cleans the input. 101 | If the user doesn't enter anything, it prompts again." 102 | [] 103 | (let [input (str/trim (read-line))] 104 | (if (empty? input) 105 | (do (println "Please enter some input") 106 | (recur)) 107 | (str/lower-case input)))) 108 | 109 | (defn parse-number 110 | [s] 111 | (Integer. (re-find #"\d+" s))) 112 | 113 | (defn buy-card 114 | "Add 1 to the bought card map for this index. 115 | Update function has to handle the special case of choosing a card 116 | for the first time." 117 | [bought index] 118 | (update bought index (fn [x] (if x (inc x) 1)))) 119 | 120 | (defn return-card 121 | "Like buy card, except subtract 1" 122 | [bought index] 123 | (update bought index (fn [x] (if (or (not x) (zero? x)) 0 (dec x))))) 124 | 125 | (defn view-card 126 | [index] 127 | (println "\n========") 128 | (-> (get cards index) 129 | format-card 130 | println) 131 | (println "========")) 132 | 133 | (defn parse-input 134 | [bought input] 135 | (let [command (subs input 0 1)] 136 | (cond (= command "b") (buy-card bought (parse-number input)) 137 | (= command "v") (do (view-card (parse-number input)) 138 | bought) 139 | (= command "r") (return-card bought (parse-number input)) 140 | (= command "reset") {} 141 | :else (do (println "I don't understand that") 142 | bought)))) 143 | 144 | (defn prompt 145 | [bought] 146 | (println "(b)uy, (r)eturn, or (v)iew a card, or (reset). Ex: \"b 0\" to buy cleric") 147 | (parse-input bought (get-input))) 148 | 149 | (defn dashboard 150 | [bought] 151 | (loop [bought bought] 152 | (println (format-card-table bought)) 153 | (println "Total cost: " (total-cost cards bought)) 154 | (let [new-bought (prompt bought)] 155 | (if new-bought 156 | (recur new-bought) 157 | (println "Goodbye!"))))) 158 | 159 | (defn -main 160 | "I don't do a whole lot ... yet." 161 | [& args] 162 | (dashboard {})) 163 | -------------------------------------------------------------------------------- /projects/valeria/src/valeria/odds.clj: -------------------------------------------------------------------------------- 1 | (ns valeria.odds) 2 | 3 | (defn sums 4 | [d1 d2] 5 | (loop [d1' d1 6 | pairs []] 7 | (if (= d1' 0) 8 | pairs 9 | (recur (dec d1') 10 | (loop [d2' d2 11 | pairs' pairs] 12 | (if (= d2' 0) 13 | pairs' 14 | (recur (dec d2') 15 | (conj pairs' (+ d1' d2'))))))))) 16 | 17 | (defn sum-odds 18 | [sum d1 d2] 19 | (let [sums (sums d1 d2)] 20 | (/ (count (filter #(= sum %) sums)) (* d1 d2)))) 21 | -------------------------------------------------------------------------------- /real/parse/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /real/parse/README.md: -------------------------------------------------------------------------------- 1 | # parse 2 | 3 | The purpose of this code is to parse CSV, ensuring that it contains 4 | all required fields. It's derived from a real-world project. 5 | 6 | To try it out, run `lein run resources/valid.csv` to see what happens 7 | when the CSV is valid, and `lein run resources/invalid.csv` to see 8 | what happens when the CSV is invalid. 9 | 10 | To understand it, start with the 11 | [`-main`](https://github.com/braveclojure/training/blob/master/real/parse/src/parse/core.clj) 12 | function and read each of the functions that `-main` calls: 13 | `read-csv`, `find-invalid`, and `report`. Try working with each 14 | function individually in a REPL if you don't understand what it does; 15 | play with it until it makes sense :) 16 | 17 | It relies on the 18 | [`clojure.data.csv`](https://github.com/clojure/data.csv) library. 19 | -------------------------------------------------------------------------------- /real/parse/project.clj: -------------------------------------------------------------------------------- 1 | (defproject parse "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [org.clojure/data.csv "0.1.3"]] 8 | :main ^:skip-aot parse.core 9 | :target-path "target/%s" 10 | :profiles {:uberjar {:aot :all}}) 11 | -------------------------------------------------------------------------------- /real/parse/resources/invalid.csv: -------------------------------------------------------------------------------- 1 | USA,1,,United States," 17,419,000 ",,,,, 2 | CHN,2,,China," 10,354,832 ",,,,, 3 | JPN,3,,Japan," 4,601,461 ",,,,, 4 | DEU,4,,Germany," 3,868,291 ",,,,, 5 | GBR,5,,United Kingdom," 2,988,893 ",,,,, 6 | FRA,6,,France," 2,829,192 ",,,,, 7 | BRA,7,,Brazil," 2,416,636 ",,,,, 8 | ITA,8,,Italy," 2,141,161 ",,,,, 9 | IND,9,,India," 2,048,517 ",,,,, 10 | RUS,10,,Russian Federation," 1,860,598 ",a,,,, 11 | CAN,11,,Canada," 1,785,387 ",,,,, 12 | AUS,12,,Australia," 1,454,675 ",,,,, 13 | KOR,13,,"Korea, Rep."," 1,410,383 ",,,,, 14 | ESP,14,,Spain," 1,381,342 ",,,,, 15 | MEX,15,,Mexico," 1,294,690 ",,,,, 16 | IDN,16,,Indonesia," 888,538 ",,,,, 17 | NLD,17,,Netherlands," 879,319 ",,,,, 18 | TUR,18,,Turkey," 798,429 ",,,,, 19 | SAU,19,,Saudi Arabia," 753,832 ",,,,, 20 | CHE,20,,Switzerland," 701,037 ",,,,, 21 | SWE,21,,Sweden," 571,090 ",,,,, 22 | NGA,22,,Nigeria," 568,508 ",,,,, 23 | POL,23,,Poland," 544,967 ",,,,, 24 | ARG,24,,Argentina," 537,660 ",,,,, 25 | BEL,25,,Belgium," 531,547 ",,,,, 26 | NOR,26,,Norway," 499,817 ",,,,, 27 | AUT,27,,Austria," 436,888 ",,,,, 28 | IRN,28,,"Iran, Islamic Rep."," 425,326 ",,,,, 29 | THA,29,,Thailand," 404,824 ",,,,, 30 | ARE,30,,United Arab Emirates," 399,451 ",,,,, 31 | VEN,31,,"Venezuela, RB"," 381,286 ",,,,, 32 | COL,32,,Colombia," 377,740 ",,,,, 33 | ZAF,,,South Africa," 350,141 ",,,,, 34 | DNK,34,,Denmark," 342,362 ",,,,, 35 | MYS,35,,Malaysia," 338,104 ",,,,, 36 | SGP,36,,Singapore," 307,860 ",,,,, 37 | ISR,37,,Israel," 305,675 ",,,,, 38 | EGY,,,"Egypt, Arab Rep."," 301,499 ",,,,, 39 | HKG,39,,"Hong Kong SAR, China"," 290,896 ",,,,, 40 | PHL,40,,Philippines," 284,777 ",,,,, 41 | FIN,41,,Finland," 272,217 ",,,,, 42 | CHL,42,,Chile," 258,062 ",,,,, 43 | IRL,43,,Ireland," 250,814 ",,,,, 44 | PAK,,,Pakistan," 243,632 ",,,,, 45 | GRC,45,,Greece," 235,574 ",,,,, 46 | PRT,4,,Portugal," 230,117 ",,,,, 47 | IRQ,47,,Iraq," 223,500 ",,,,, 48 | KAZ,48,,Kazakhstan," 217,872 ",,,,, 49 | DZA,49,,Algeria," 213,518 ",,,,, 50 | QAT,50,,Qatar," 210,109 ",,,,, 51 | CZE,51,,Czech Republic," 205,270 ",,,,, 52 | PER,52,,Peru," 202,596 ",,,,, 53 | NZL,53,,New Zealand," 199,970 ",,,,, 54 | ROM,54,,Romania," 199,044 ",,,,, 55 | VNM,55,,Vietnam," 186,205 ",,,,, 56 | BGD,56,,Bangladesh," 172,887 ",,,,, 57 | KWT,57,,Kuwait," 163,612 ",,,,, 58 | AGO,58,,Angola," 138,357 ",,,,, 59 | HUN,59,,Hungary," 138,347 ",,,,, 60 | UKR,60,,Ukraine," 131,805 ",a,,,, 61 | MAR,61,,Morocco," 110,009 ",b,,,, 62 | PRI,62,,Puerto Rico," 103,135 ",,,,, 63 | ECU,63,,Ecuador," 100,917 ",,,,, 64 | SVK,64,,Slovak Republic," 100,249 ",,,,, 65 | OMN,65,,Oman," 81,797 ",,,,, 66 | LKA,66,,Sri Lanka," 78,824 ",,,,, 67 | CUB,67,,Cuba," 77,150 ",,,,, 68 | BLR,68,,Belarus," 76,139 ",,,,, 69 | AZE,69,,Azerbaijan," 75,198 ",,,,, 70 | SDN,70,,Sudan," 73,815 ",,,,, 71 | LUX,71,,Luxembourg," 64,874 ",,,,, 72 | MMR,72,,Myanmar," 64,330 ",,,,, 73 | DOM,73,,Dominican Republic," 64,138 ",,,,, 74 | UZB,74,,Uzbekistan," 62,644 ",,,,, 75 | KEN,75,,Kenya," 60,937 ",,,,, 76 | GTM,76,,Guatemala," 58,827 ",,,,, 77 | URY,77,,Uruguay," 57,471 ",,,,, 78 | HRV,78,,Croatia," 57,113 ",,,,, 79 | BGR,79,,Bulgaria," 56,717 ",,,,, 80 | ETH,80,,Ethiopia," 55,612 ",,,,, 81 | MAC,81,,"Macao SAR, China"," 55,502 ",,,,, 82 | CRI,82,,Costa Rica," 49,553 ",,,,, 83 | SVN,83,,Slovenia," 49,491 ",,,,, 84 | TUN,84,,Tunisia," 48,613 ",,,,, 85 | LTU,85,,Lithuania," 48,354 ",,,,, 86 | TZA,86,,Tanzania," 48,057 ",c,,,, 87 | TKM,87,,Turkmenistan," 47,932 ",,,,, 88 | PAN,88,,Panama," 46,213 ",,,,, 89 | LBN,89,,Lebanon," 45,731 ",,,,, 90 | SRB,90,,Serbia," 43,866 ",,,,, 91 | LBY,91,,Libya," 41,143 ",,,,, 92 | GHA,92,,Ghana," 38,617 ",,,,, 93 | YEM,93,,"Yemen, Rep."," 35,955 ",,,,, 94 | JOR,94,,Jordan," 35,827 ",,,,, 95 | CIV,95,,Côte d'Ivoire," 34,254 ",,,,, 96 | BHR,96,,Bahrain," 33,851 ",,,,, 97 | ZAR,97,,"Congo, Dem. Rep."," 33,121 ",,,,, 98 | BOL,98,,Bolivia," 32,996 ",,,,, 99 | CMR,99,,Cameroon," 32,051 ",,,,, 100 | LVA,100,,Latvia," 31,287 ",,,,, 101 | PRY,101,,Paraguay," 30,881 ",,,,, 102 | TTO,102,,Trinidad and Tobago," 28,883 ",,,,, 103 | ZMB,103,,Zambia," 27,066 ",,,,, 104 | UGA,104,,Uganda," 26,998 ",,,,, 105 | EST,105,,Estonia," 26,485 ",,,,, 106 | SLV,106,,El Salvador," 25,164 ",,,,, 107 | CYP,107,,Cyprus," 23,226 ",d,,,, 108 | AFG,108,,Afghanistan," 20,038 ",,,,, 109 | NPL,109,,Nepal," 19,770 ",,,,, 110 | HND,110,,Honduras," 19,385 ",,,,, 111 | BIH,111,,Bosnia and Herzegovina," 18,521 ",,,,, 112 | GAB,112,,Gabon," 18,180 ",,,,, 113 | BRN,113,,Brunei Darussalam," 17,105 ",,,,, 114 | ISL,114,,Iceland," 17,036 ",,,,, 115 | PNG,115,,Papua New Guinea," 16,929 ",,,,, 116 | KHM,116,,Cambodia," 16,778 ",,,,, 117 | GEO,117,,Georgia," 16,530 ",e,,,, 118 | MOZ,118,,Mozambique," 15,938 ",,,,, 119 | BWA,119,,Botswana," 15,813 ",,,,, 120 | SEN,120,,Senegal," 15,658 ",,,,, 121 | GNQ,121,,Equatorial Guinea," 15,530 ",,,,, 122 | ZWE,122,,Zimbabwe," 14,197 ",,,,, 123 | COG,123,,"Congo, Rep."," 14,177 ",,,,, 124 | TCD,124,,Chad," 13,922 ",,,,, 125 | JAM,125,,Jamaica," 13,891 ",,,,, 126 | SSD,126,,South Sudan," 13,282 ",,,,, 127 | ALB,127,,Albania," 13,212 ",,,,, 128 | NAM,128,,Namibia," 12,995 ",,,,, 129 | WBG,129,,West Bank and Gaza," 12,738 ",,,,, 130 | MUS,130,,Mauritius," 12,630 ",,,,, 131 | BFA,131,,Burkina Faso," 12,542 ",,,,, 132 | MLI,132,,Mali," 12,037 ",,,,, 133 | MNG,133,,Mongolia," 12,016 ",,,,, 134 | LAO,134,,Lao PDR," 11,997 ",,,,, 135 | NIC,135,,Nicaragua," 11,806 ",,,,, 136 | ARM,136,,Armenia," 11,644 ",,,,, 137 | MKD,137,,"Macedonia, FYR"," 11,324 ",,,,, 138 | MDG,138,,Madagascar," 10,593 ",,,,, 139 | MLT,139,,Malta," 9,643 ",,,,, 140 | BEN,140,,Benin," 9,575 ",,,,, 141 | TJK,141,,Tajikistan," 9,242 ",,,,, 142 | HTI,142,,Haiti," 8,713 ",,,,, 143 | BHS,143,,"Bahamas, The"," 8,511 ",,,,, 144 | NER,144,,Niger," 8,169 ",,,,, 145 | MDA,145,,Moldova," 7,962 ",f,,,, 146 | RWA,146,,Rwanda," 7,890 ",,,,, 147 | KGZ,147,,Kyrgyz Republic," 7,404 ",,,,, 148 | KSV,148,,Kosovo," 7,387 ",,,,, 149 | GIN,149,,Guinea," 6,624 ",,,,, 150 | SOM,150,,Somalia," 5,707 ",,,,, 151 | BMU,151,,Bermuda," 5,574 ",,,,, 152 | LIE,152,,Liechtenstein," 5,488 ",,,,, 153 | SUR,153,,Suriname," 5,210 ",,,,, 154 | MRT,154,,Mauritania," 5,061 ",,,,, 155 | SLE,155,,Sierra Leone," 4,838 ",,,,, 156 | MNE,156,,Montenegro," 4,588 ",,,,, 157 | FJI,157,,Fiji," 4,532 ",,,,, 158 | TGO,158,,Togo," 4,518 ",,,,, 159 | SWZ,159,,Swaziland," 4,413 ",,,,, 160 | BRB,160,,Barbados," 4,355 ",,,,, 161 | MWI,161,,Malawi," 4,258 ",,,,, 162 | ADO,162,,Andorra," 3,249 ",,,,, 163 | GUY,163,,Guyana," 3,097 ",,,,, 164 | BDI,164,,Burundi," 3,094 ",,,,, 165 | MDV,165,,Maldives," 3,062 ",,,,, 166 | FRO,166,,Faroe Islands," 2,613 ",,,,, 167 | GRL,167,,Greenland," 2,441 ",,,,, 168 | LSO,168,,Lesotho," 2,181 ",,,,, 169 | LBR,169,,Liberia," 2,013 ",,,,, 170 | BTN,170,,Bhutan," 1,959 ",,,,, 171 | CPV,171,,Cabo Verde," 1,871 ",,,,, 172 | CAF,172,,Central African Republic," 1,723 ",,,,, 173 | BLZ,173,,Belize," 1,699 ",,,,, 174 | DJI,174,,Djibouti," 1,589 ",,,,, 175 | SYC,175,,Seychelles," 1,423 ",,,,, 176 | TMP,176,,Timor-Leste," 1,417 ",,,,, 177 | LCA,177,,St. Lucia," 1,404 ",,,,, 178 | ATG,178,,Antigua and Barbuda," 1,221 ",,,,, 179 | SLB,179,,Solomon Islands," 1,158 ",,,,, 180 | GNB,180,,Guinea-Bissau," 1,022 ",,,,, 181 | GRD,181,,Grenada, 912 ,,,,, 182 | KNA,182,,St. Kitts and Nevis, 852 ,,,,, 183 | GMB,183,,"Gambia, The", 851 ,,,,, 184 | VUT,184,,Vanuatu, 815 ,,,,, 185 | WSM,185,,Samoa, 800 ,,,,, 186 | VCT,186,,St. Vincent and the Grenadines, 729 ,,,,, 187 | COM,187,,Comoros, 624 ,,,,, 188 | DMA,188,,Dominica, 524 ,,,,, 189 | TON,189,,Tonga, 434 ,,,,, 190 | STP,190,,São Tomé and Principe, 337 ,,,,, 191 | FSM,191,,"Micronesia, Fed. Sts.", 318 ,,,,, 192 | PLW,192,,Palau, 251 ,,,,, 193 | MHL,193,,Marshall Islands, 187 ,,,,, 194 | KIR,194,,Kiribati, 167 ,,,,, 195 | TUV,195,,Tuvalu, 38 ,,,,, -------------------------------------------------------------------------------- /real/parse/resources/valid.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braveclojure/training/5b7fb9059c17b2166c2e66850094f424319e55eb/real/parse/resources/valid.csv -------------------------------------------------------------------------------- /real/parse/src/parse/core.clj: -------------------------------------------------------------------------------- 1 | (ns parse.core 2 | (:require [clojure.data.csv :as csv] 3 | [clojure.java.io :as io]) 4 | (:gen-class)) 5 | 6 | (defn report 7 | [errs] 8 | (if (empty? errs) 9 | "No errors!" 10 | (str "Errors at these rows: " (clojure.string/join ", " errs)))) 11 | 12 | (def data-indices 13 | "These columns must not be empty" 14 | [0 1 3 4]) 15 | 16 | (defn row-valid? 17 | "Check that each column in data-indices is not empty" 18 | [row] 19 | (every? #(not-empty (get row %)) 20 | data-indices)) 21 | 22 | (defn find-invalid 23 | [rows] 24 | (->> rows 25 | (map-indexed vector) 26 | (reduce (fn [errs [index row]] 27 | (if (row-valid? row) 28 | errs 29 | (conj errs index))) 30 | []))) 31 | 32 | (defn read-csv 33 | [path] 34 | (with-open [file (io/reader path)] 35 | (doall (csv/read-csv file)))) 36 | 37 | (defn -main 38 | "I don't do a whole lot ... yet." 39 | [file-path] 40 | (-> file-path 41 | read-csv 42 | find-invalid 43 | report 44 | println)) 45 | -------------------------------------------------------------------------------- /real/transform/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /real/transform/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | 8 | ## [0.1.1] - 2016-05-28 9 | ### Changed 10 | - Documentation on how to make the widgets. 11 | 12 | ### Removed 13 | - `make-widget-sync` - we're all async, all the time. 14 | 15 | ### Fixed 16 | - Fixed widget maker to keep working when daylight savings switches over. 17 | 18 | ## 0.1.0 - 2016-05-28 19 | ### Added 20 | - Files from the new template. 21 | - Widget maker public API - `make-widget-sync`. 22 | 23 | [Unreleased]: https://github.com/your-name/transform/compare/0.1.1...HEAD 24 | [0.1.1]: https://github.com/your-name/transform/compare/0.1.0...0.1.1 25 | -------------------------------------------------------------------------------- /real/transform/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /real/transform/README.md: -------------------------------------------------------------------------------- 1 | # transform 2 | 3 | `core.clj` includes some example real-world code that shows how you 4 | might transform data after pulling it from the database. 5 | 6 | The code maps over "flights", records pulled from a database, using 7 | `process-flight` as the mapping function. `process-flight` just 8 | bundles together functions that process a specific aspect of the the 9 | flight, `format-flight-times` and `add-isfreqcap`. 10 | 11 | As it is, you can't run this code from the command line, but you can 12 | interact with it in a REPL. 13 | 14 | To understand the code, start with 15 | [line 46](https://github.com/braveclojure/training/blob/master/real/transform/src/transform/core.clj#L46). This 16 | actually applies the `map` function to `example-flights`, transforming 17 | them using the `process-flight` function. 18 | 19 | From there, read the 20 | [definition of the `process-flight`](https://github.com/braveclojure/training/blob/master/real/transform/src/transform/core.clj#L40) 21 | function. It takes one argument, a `flight`, and passes it to the 22 | `format-flight-times` function. The result of that is passed to 23 | `add-isfreqcap` 24 | 25 | `format-flight-times` modifies two fields in the map, transforming 26 | them from one textual representation of a date to 27 | another. `add-isfreqcap` checks whether the `:freqcap` key of a flight 28 | is truthy, and if it is, associates the flight with the `:isfreqcap 29 | true` key/value pair; otherwise it returns the original flight. 30 | 31 | 32 | This code relies on the `clj-time` library, which is the most-used 33 | library for dealing with dates and times. 34 | -------------------------------------------------------------------------------- /real/transform/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to transform 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /real/transform/project.clj: -------------------------------------------------------------------------------- 1 | (defproject transform "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"] 7 | [clj-time "0.11.0"]] 8 | :main ^:skip-aot transform.core 9 | :target-path "target/%s" 10 | :profiles {:uberjar {:aot :all}}) 11 | -------------------------------------------------------------------------------- /real/transform/src/transform/core.clj: -------------------------------------------------------------------------------- 1 | (ns transform.core 2 | (:require [clj-time.format :as f])) 3 | 4 | (def example-flights 5 | [{:partingstarttime "08:20:00.00000" 6 | :partingendtime "22:00:00.00000" 7 | :name "Flight 1"} 8 | {:partingstarttime "06:00:00.00000" 9 | :partingendtime "20:15:00.00000" 10 | :freqcap true 11 | :name "Flight 2"}]) 12 | 13 | (def input-date-format 14 | (f/formatter "HH:mm:ss")) 15 | 16 | (def output-date-format 17 | (f/formatter "hh:mm a")) 18 | 19 | (defn format-parting 20 | "turns something like 23:00:00.00000 into 11:00 PM" 21 | [time-string] 22 | (if time-string 23 | (->> (clojure.string/replace time-string #"\.0+" "") 24 | (f/parse input-date-format) 25 | (f/unparse output-date-format)))) 26 | 27 | (defn format-flight-times 28 | [flight] 29 | (-> flight 30 | (update-in [:partingstarttime] format-parting) 31 | (update-in [:partingendtime] format-parting))) 32 | 33 | (defn add-isfreqcap 34 | "This appears redundant but unfortunately we needed to do it" 35 | [flight] 36 | (if (:freqcap flight) 37 | (assoc flight :isfreqcap true) 38 | flight)) 39 | 40 | (defn process-flight 41 | [flight] 42 | (-> flight 43 | format-flight-times 44 | add-isfreqcap)) 45 | 46 | (map process-flight example-flights) 47 | -------------------------------------------------------------------------------- /real/transform/test/transform/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns transform.core-test 2 | (:require [clojure.test :refer :all] 3 | [transform.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | --------------------------------------------------------------------------------