; -*- clojure -*- ; @@PLEAC@@_NAME ; @@SKIP@@ Clojure ; @@PLEAC@@_WEB ; @@SKIP@@ http://clojure.org/ ; @@PLEAC@@_INTRO ; @@SKIP@@ You need version 1.3.0 or above for this code to work. ;; @@PLEAC@@_1.0 Introduction ;; --------------------------- (def string "\\n") ; two characters, \ and an n (def string "Jon 'Maddog' Orwant") ; literal single quotes ;; --------------------------- (def string "\n") ; "newline" character (def string "Jon \"Maddog\" Orwant") ; literal double quotes (def a " This is a multiline here document terminated by one double quote. ") ;; @@PLEAC@@_1.1 Accessing Substrings (def value (subs string offset (+ offset count))) (def value (subs string offset (count string))) ;; or (def value (subs string offset)) ;; Clojure strings are immutable Java strings, so while you cannot ;; modify an existing string, you can build a new one with part of it ;; replaced by another. (def string (str (subs string 0 offset) newstring (subs string (+ offset count)))) (def string (str (subs string 0 offset) newtail)) ;; ----------------------------- ;; get a 5-byte string, skip 3, then grab 2 8-byte strings, then the rest ;; split at 'sz' byte boundaries ;; jli for mbac: partition is the bomb for this ;; mbac for jli: hell yeah! ;; jli for mbac: I meant, "partition" is old and tired. all the cool ;; kids are using "partition-all". see commify-hipster. (defn split-every-n-chars [sz string] (if (empty? string) () (try (let [beg (subs string 0 sz) rest (subs string sz)] (cons beg (split-every-n-chars sz rest))) (catch Exception _e [string])))) ;; or the more idiomatic version (defn split-every-n-chars [sz string] ;; the map turns vector of char vector into vector of string (map (fn [x] (apply str x)) (partition 5 5 nil string))) (def fivers (split-every-n-chars 5 string)) ;; chop string into individual characters (def chars (seq string)) ;; ----------------------------- (def string "This is what you have") ;; Indexes are left to right. There is no possibility to index ;; directly from right to left ;; "T" (def first (subs string 0 1)) ;; "is" (def start (subs string 5 7)) ;; "you have" (def rest (subs string 13)) ;; "e" *) (def last (let [len (count string)] (subs string (- len 1)))) ;; "have" (def theend (let [len (count string)] (subs string (- len 4)))) ;; "you" (def piece (let [len (count string)] (subs string (- len 8) (- len 5)))) ;; ----------------------------- (def string "This is what you have") (printf "%s" "string") ;; Change "is" to "wasn't" (def string (str (subs string 0 5) "wasn't" (subs string 7))) ;; This wasn't what you have ;; This wasn't wonderous (def string (str (subs string 0 (- (count string) 12)) "ondrous")) ;; delete first character (def string (subs string 1)) ;; his wasn't wondrous ;; delete last 10 characters (def string (subs string 0 (- (count string) 10))) ;; his wasn' ;; ----------------------------- ;; @@PLEAC@@_1.2 Establishing a Default Value ;; While Perl treats undef, 0, and "" as false, Clojure treats the ;; values false and nil as false, but 0 and "" as true. ;; ----------------------------- ;; use b if b is true, else c ;; Note that if b has never been defined or had a value bound to it, ;; then unlike Perl this will give an error that the value is ;; undefined. (def a (or b c)) ;; re-define x with the value y, unless x is already true (def x (when-not x y)) ;; use b if b is defined, otherwise c ;; This correctly tests whether b is bound to a value or not, but ;; if it is not, then it throws an exception because of the last ;; occurrence of b not having a value. (def a (if (find (ns-interns *ns*) 'b) b c)) ;; This is closer: (def a (if (find (ns-interns *ns*) 'b) (eval 'b) c)) ;; But note that if b is only bound in a let, or as a function ;; argument, but not at the top level with def or something similar, ;; then this code will go with the value of c. (let [c "c-value" b "b-value"] (let [a (if (find (ns-interns *ns*) 'b) (eval 'b) c)] (printf "a=%s" a))) ;; a=c-value ;; ----------------------------- (def foo (or bar "DEFAULT VALUE")) ;; Clojure data structures are immutable. The code below does not ;; change the value of *command-line-args*, whereas Perl ;; 'shift(@ARGV)' does modify @ARGV by removing its first element. (def dir (if (>= (count *command-line-args*) 1) (nth *command-line-args* 0) "/tmp")) ;; @@PLEAC@@_1.3 Exchanging Values Without Using Temporary Variables ;; ----------------------------- ;; This Clojure code does _not_ exchange values of var1 and var2 ;; without a temporary. It binds var1 to the value of var2, then ;; binds var2 to the new value of var1, so they both end up with the ;; original value of var2. (let [var1 var2 var2 var1]) ;; This will achieve the desired effect. It creates a vector of the ;; values of var2 and var1, then binds them using a technique called ;; 'destructuring' to var1 and var2. (let [[var1 var2] [var2 var1]]) ;; ----------------------------- (def temp a) (def a b) (def b temp) ;; ----------------------------- (let [a "alpha" b "omega"] (let [[a b] [b a]] ;; the first shall be last -- and versa vice )) ;; ----------------------------- (let [alpha "January" beta "March" production "August"] ;; move beta to alpha ;; move production to beta ;; move alpha to production (let [[alpha beta production] [beta production alpha]] )) ;; @@PLEAC@@_1.4 Converting Between ASCII Characters and Values ;; ----------------------------- (def num (int \a)) ; => ASCII code 97 (def char (char 97)) ; => \a ;; ----------------------------- (defn print-ascii-code-for-char [c] (printf "Number %d is character '%c'\n" (int c) c)) ;; (print-ascii-code-for-char \a) ;; Number 97 is the ASCII character a ;; @@PLEAC@@_1.5 Processing a String One Character at a Time ;; Strings in Clojure can be treated as sequences, so the usual ;; map, reduce, doseq functions apply. (defn one-char-at-a-time [f string] (doseq [b string] (f b))) ;; => (one-char-at-a-time ;; (fn [b] (printf "do something with: %c\n" b)) ;; "abc") ;; do something with: a ;; do something with: b ;; do something with: c ;; ---------------------------- (defn print-uniq-chars [string] (printf "unique chars are: %s\n" (sort (set string)))) ;; => (print-uniq-chars "an apple a day") ;; unique chars are: (\space \a \d \e \l \n \p \y) ;; ----------------------------- (defn print-ascii-value-sum [string] (printf "sum is %s\n" (apply + (map int string)))) ;; => (print-ascii-value-sum "an apple a day") ;; sum is 1248 ;; ----------------------------- ;; @@PLEAC@@_1.6 Reversing a String by Word or Character ;; ----------------------------- ;; Make namespace clojure.string usable with the abbreviated name ;; 'str'. (require '[clojure.string :as str]) (def revbytes (str/reverse string)) ;; ----------------------------- ;; TBD: Verify whether the split call below matches the behavior of ;; Perl split with a " " as first arg. Should we use the regular ;; expression #"\s+" to match Perl behavior more closely? Does that ;; even match exactly? What about white space before first word or ;; after last word in the string to be split? (str/join " " (reverse (str/split str #"\s+"))) ;; ----------------------------- (def gnirts (str/reverse string)) ; str/reverse reverses letters in string (def sdrow (reverse words)) ; reverse reverses elements in sequence ;; TBD: What corresponds to following Perl? ;; $confused = reverse(@words); # reverse letters in join("", @words) ;; ----------------------------- ;; reverse word order (def string "Yoda said, \"can you see this?\"") (def allwords (str/split string #"\s+")) (def revwords (str/join " " (reverse allwords))) (printf "%s\n" revwords) ;; ----------------------------- ;; There is no shortcut in Clojure like in Perl for the last arg of ;; str/split equal to " " meaning the same thing as matching on the ;; regular expression #"\s+" ;; ----------------------------- (def revwords (str/join " " (reverse (str/split str #"\s+")))) ;; ----------------------------- (def word "reviver") (def is-palindrome (= word (str/reverse word))) ;; ----------------------------- ;; No Clojure program I know of equivalent to Perl's for finding ;; palindromes of length 5 or larger conveniently fits into a single ;; line. Better to create a file with this program. (ns print-palindromes (:require [clojure.string :as str] [clojure.java.io :as io])) ;; mbac for jafingerhut: don't you need to close the file handle returned by io/reader? (doseq [filename *command-line-args*] (doseq [line (line-seq (io/reader filename))] (when (and (= line (str/reverse line)) (>= (count line) 5)) (printf "%s\n" line)))) ;; Save the above in a file print-palindromes.clj, then run from ;; command prompt (replace path to wherever your clojure-1.3.0.jar ;; file is located): ;; % java -cp /Users/jafinger/lein/clj-1.3.0/lib/clojure-1.3.0.jar clojure.main print-palindromes.clj /usr/share/dict/words ;; Alternately, on Linux/*BSD/Mac OS X, create a shell script like ;; this: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; #! /bin/sh ;; ;; # Replace the path below to refer to the Clojure jar file on your system. ;; CLJ_JAR=$HOME/lein/clj-1.3.0/lib/clojure-1.3.0.jar ;; scriptname="$1" ;; shift ;; java -cp $CLJ_JAR:. clojure.main "$scriptname" "$@" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; If you save that as the file 'clj' somewhere in your command path ;; and make it executable (remove the ';; ' before each line), and add ;; this line to the beginning of print-palindromes.clj: ;; ;; #! /usr/bin/env clj ;; ;; Then you can use the command line: ;; ;; % ./print-palindromes.clj /usr/share/dict/words ;; ;; or if print-palindromes.clj is in your command path (perhaps ;; because . is in your command path, although I wouldn't recommend it ;; for security reasons): ;; ;; % print-palindromes.clj /usr/share/dict/words ;; @@PLEAC@@_1.7 Reversing a String by Word or by Character ;; ----------------------------- ;; Clojure's built-in regexp matching functions have something like ;; Perl's $& that returns everything that a regexp matched within a ;; string, and also like Perl's $1, $2, $3, etc. that match ;; parenthesized groups. However, it seems to require calls to Java ;; methods to get something like Perl's $` and $' that return the ;; strings that are before and after the regexp match. ;; The Clojure function expand-str below is most closely analagous to ;; the following Perl code: ;; sub expand_str { ;; my $s = shift; ;; while (1) { ;; if ($s =~ /\t+/) { ;; $s = $` . (' ' x (length($&) * 8 - length($`) % 8)) . $'; ;; } else { ;; return $s; ;; } ;; } ;; } (defn expand-str [s] (loop [s s] (let [m (re-matcher #"\t+" s) tabs (re-find m)] ; Like Perl's $& (if tabs (let [before-tabs (subs s 0 (. m (start))) ; Like Perl's $` after-tabs (subs s (. m (end)))] ; $' (recur (str before-tabs (apply str (repeat (- (* (count tabs) 8) (mod (count before-tabs) 8)) " ")) after-tabs))) s)))) ;; Performance note: The code above will recompile the regexp #"\t+" ;; each time through the loop. If you want it to be compiled only ;; once, wrap the function body in a (let [pat #"\t+"] ...) and use ;; pat in place of #"\t+" in the body. ;; Another way is to use the regexp "^([^\t]*)(\t+)" instead of simply ;; "\t+". The ([^\t]*) will explicitly match everything before the ;; first tabs. Warning: using (.*) instead would greedily match as ;; much of the beginning of the string as possible, including tabs, so ;; would not correctly cause (\t+) to match the _first_ tabs in the ;; string. ;; According to http://dev.clojure.org/jira/browse/CLJ-753 there is a ;; bug in str/replace-first where it returns nil instead of the ;; unmodified string s if the regexp pattern is not found to match ;; anywhere in s. ;; replace-first-fixed is a modified version of str/replace-first that ;; behaves as the corrected version should. (defn replace-first-fixed [s pat fn] (if-let [new-s (str/replace-first s pat fn)] new-s s)) ;; The last argument to str/replace-first is a fn that takes a vector ;; of strings as an argument. The first of these strings is ;; everything that was matched by the regexp pattern. The rest are ;; the strings matched by parenthesized groups inside the regexp. We ;; use Clojure's destructuring on function arguments to break up the ;; vector argument to replace-tabs and give names to its elements. (defn replace-tabs [[all-matched before-tabs tabs]] (str before-tabs (apply str (repeat (- (* (count tabs) 8) (mod (count before-tabs) 8)) " ")))) ;; Repeatedly call replace-first-fixed until the string does not ;; change, indicating that no match was found. (defn expand-str [s] (loop [s s] (let [next-s (replace-first-fixed s #"^([^\t]*)(\t+)" replace-tabs)] (if (= s next-s) s (recur next-s))))) ;; Performance note: Same as above about the regexp being recompiled ;; every time through the loop. Bind the regexp to a symbol using ;; let, outside of the loop, to compile it only once. ;; My favorite version of this requires defining slightly modified ;; versions of clojure.core/re-groups and clojure.string/replace-first ;; The modified re-groups+ returns a vector like (re-groups) does, ;; except it always returns a vector, even if there are no ;; parenthesized subexpressions in the regexp, and it always returns ;; the part of the string before the match (Perl's $`) as the first ;; element, and the part of the string after the match (Perl's $') as ;; the last element. (defn re-groups+ [^java.util.regex.Matcher m s] (let [gc (. m (groupCount)) pre (subs s 0 (. m (start))) post (subs s (. m (end)))] (loop [v [pre] c 0] (if (<= c gc) (recur (conj v (. m (group c))) (inc c)) (conj v post))))) ;; replace-first+ is based on Clojure's hidden internal function ;; replace-first-by, except that it calls the user-supplied fn f for ;; calculating the replcement string with the return value of ;; re-groups+ instead of re-groups, so f can use those additional ;; strings to calculate the replacement. ;; The other difference is that it returns a vector of two elements: ;; the first is the string matched, or nil if there was no match. The ;; second is the string after replacement on a match, or the original ;; string if no match. (defn replace-first+ [^CharSequence s ^java.util.regex.Pattern re f] (let [m (re-matcher re s)] (let [buffer (StringBuffer. (.length s))] (if (.find m) (let [groups (re-groups+ m s) rep (f groups)] (.appendReplacement m buffer rep) (.appendTail m buffer) [(second groups) (str buffer)]) [nil s])))) ;; Assuming the above are added to Clojure, or some user-defined ;; library of commonly-used utilities, the "new code" is as follows: (defn expand-str [s] (loop [[found-match s] [true s]] (if found-match (recur (replace-first+ s #"\t+" (fn [[pre tabs post]] (apply str (repeat (- (* (count tabs) 8) (mod (count pre) 8)) " "))))) s))) ;; Performance note: As before, assign the regexp #"\t+" to a symbol ;; using let, outside of the loop, to compile it only once, instead of ;; every time through the loop. ;; Test cases: ;; (let [t1 (= "No tabs here" (expand-str "No tabs here")) ;; t2 (= "Expand this" (expand-str "Expand\t\tthis")) ;; t3 (= "Expand this please" (expand-str "Expand\t\tthis\tplease"))] ;; [t1 t2 t3]) ;; ----------------------------- ;; I am not aware of any Clojure library similar to Perl's Text::Tabs ;; The expand-str Clojure functions above work on individual strings. ;; This works on a string or a collection of strings, similar to how ;; Perl's does: (defn expand [x] (cond (instance? String x) (expand-str x) :else (map expand-str x))) (defn unexpand-line [s] (let [s (expand s) len (count s) tabstop *tabstop* sections (map #(subs s % (min len (+ % tabstop))) (range 0 len tabstop)) ;; last section must be handled differently than earlier ones lastbit (last sections) sections (butlast sections) lastbit (if (and (= (count lastbit) tabstop) (str/blank? lastbit)) "\t" lastbit) sections (map #(str/replace % #" +$" "\t") sections) sections (conj (vec sections) lastbit)] (str/join "" sections))) (defn unexpand-str [s] (str/join "\n" (map unexpand-line (str/split s #"\n" -1)))) (defn unexpand [x] (cond (instance? String x) (unexpand-str x) :else (map unexpand-str x))) ;; ----------------------------- (ns expand (:require [clojure.string :as str] [clojure.java.io :as io])) ;; Use your preferred version of expand here. (doseq [filename *command-line-args*] (doseq [line (line-seq (io/reader filename))] (printf "%s\n" (expand line)))) ;; ----------------------------- ;; Below is a version of expand-str that takes an optional argument ;; tabstop. It is based upon the last version of expand-str given ;; above, but the others could easily be generalized in a similar way. (defn expand-str ([s tabstop] (loop [[found-match s] [true s]] (if found-match (recur (replace-first+ s #"\t+" (fn [[pre tabs post]] (apply str (repeat (- (* (count tabs) tabstop) (mod (count pre) tabstop)) " "))))) s))) ([s] (expand-str s 8))) ;; If one wished for a version of expand-str that could use a tabstop ;; supplied by a "global variable", then a dynamic var named *tabstop* ;; that was used inside of expand-str would be a good way to do it. (def ^:dynamic *tabstop* 8) (defn expand-str [s] (loop [[found-match s] [true s]] (if found-match (recur (replace-first+ s #"\t+" (fn [[pre tabs post]] (apply str (repeat (- (* (count tabs) *tabstop*) (mod (count pre) *tabstop*)) " "))))) s))) (expand-str "Expand\t\tthis") ; expands to tabstop 8 (def ^:dynamic *tabstop* 4) (expand-str "Expand\t\tthis") ; expands to tabstop 4 this time ;; Performance note: Besides the repeated one about avoiding ;; recompilation of the regexp, a new performance issue here is that ;; accessing dynamic vars like *tabstop* is slower than accessing a ;; local binding like those introduced via let or loop. Wrapping the ;; entire function body in something like (let [tabstop *tabstop*] ;; ... ) and using tabstop in place of *tabstop* inside the body ;; incurs this cost only once, instead of every time through the loop. ;; ----------------------------- (ns unexpand (:require [clojure.string :as str] [clojure.java.io :as io])) ;; Use your preferred version of expand and unexpand here. (doseq [filename *command-line-args*] (doseq [line (line-seq (io/reader filename))] (printf "%s\n" (unexpand line)))) ;; ----------------------------- ;; 1.8 Expanding and Compressing Tabs ;; 1.9 Expanding Variables in User Input ;; @PLEAC@@_1.10 Controlling Case (.toUpperCase "foo") ;; -> "FOO" (.toLowerCase "FOO") ;; -> "foo" ;; 1.11 Interpolating Functions and Expressions Within Strings ;; 1.12 Indenting Here Documents ;; 1.13 Reformatting Paragraphs ;; 1.14 Escaping Characters ;; 1.15 Trimming Blanks from the Ends of a String (.trim string) ;; (.trim " foo ") => "foo" ;; 1.16 Parsing Comma-Separated Data ;; @@PLEAC@@_2.1 Checking Whether a String Is a Valid Number (import '(java.text NumberFormat ParseException) '(java.util Locale)) (def locale Locale/US) (defn nb [s] (let [nf (NumberFormat/getInstance locale)] (.parse nf s))) ;; user=> (nb "100") ;; 100 ;; user=> (nb "not a number") ;; java.text.ParseException: Unparseable number: "not a number" ;; (def s1 "100") ;; (def s1 "not a number") (try (Integer/parseInt s1) (catch NumberFormatException _ex (println (str s1 " is not an integer")))) ;; (def s2 3.14) ;; (def s2 "foo") (try (Float/parseFloat s2) (catch NumberFormatException _ex (println (str s2 " is not a float")))) (defn isNumeric [s] (let [nf (NumberFormat/getInstance locale)] (try (do (.parse nf s) true) (catch ParseException _ex false)))) ;; @@PLEAC@@_2.2 Comparing Floating-Point Numbers ;;---------------------------------------------------------------------------------- ;; (equal NUM1 NUM2 ACCURACY) returns true if NUM1 and NUM2 are ;; equal to ACCURACY number of decimal places ;; jli for mbac: not sure if you can use with-precision for this: ;; http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/with-precision (defn equal [num1 num2 accuracy] (letfn [(bignum [num] (.setScale (BigDecimal. num) accuracy BigDecimal/ROUND_DOWN))] (= 0 (.compareTo (bignum num1) (bignum num2))))) ;;---------------------------------------------------------------------------------- ;; with a scaling factor ;; use "M" suffix for BigDecimal literals (def wage 5.36M) (def hours 40M) (def week (.multiply wage hours)) (println (str "One week's wage is: $" week)) ;; One week's wage is: $214.40 ;;---------------------------------------------------------------------------------- ;; @@PLEAC@@_2.3 Rounding Floating-Point Numbers ;;---------------------------------------------------------------------------------- ;; (def unrounded ...) ;; (def scale ...) ;; (def roundingMode ...) (def rounded (.setScale unrounded scale roundingMode)) ;;---------------------------------------------------------------------------------- (def a 0.255M) (def b (.setScale a 2 BigDecimal/ROUND_HALF_UP)) (println (str "Unrounded: " a)) (println (str "Rounded: " b)) ;=> Unrounded: 0.255 ;=> Rounded: 0.26 ;;---------------------------------------------------------------------------------- ;; caution, Math.rint() rounds to the nearest integer! (def a [3.3 3.5 3.7 -3.3]) (println "number\tint\tfloor\ceil") (map (fn [x] (println (str (Math/rint x) "\t" (Math/floor x) "\t" (Math/ceil x)))) a) ;; 3.0 3.0 4.0 ;; 4.0 3.0 4.0 ;; 4.0 3.0 4.0 ;; -3.0 -4.0 -3.0 ;; @@PLEAC@@_2.4 Converting Between Binary and Decimal ;;---------------------------------------------------------------------------------- (def i (Integer/parseInt s 2)) ;;---------------------------------------------------------------------------------- (def s (Integer/toString i 2)) ;;---------------------------------------------------------------------------------- ;; reader supports "<radix>r<number>" (def i 2r0110110) ; i = 54 ;;---------------------------------------------------------------------------------- (def s (Integer/toString 54 2)) ; s = 110110 ;;---------------------------------------------------------------------------------- ;; @@PLEAC@@_2.5 Operating on a Series of Integers ;;---------------------------------------------------------------------------------- (let [x 1 y 10] (doseq [i (range x (inc y))] ;; i is set to every integer from X to Y inclusive )) (let [x 1 y 10] (doseq [i (range x (+ y 1) 7)] ;; i is set to every integer from X to Y, stepsize = 7 )) ;;---------------------------------------------------------------------------------- (apply println (cons "Infancy is:" (range 0 3))) (apply println (cons "Toddling is:" (range 3 5))) (apply println (cons "Childhood is:" (range 5 13))) ;; Infancy is: 0 1 2 ;; Toddling is: 3 4 ;; Childhood is: 5 6 7 8 9 10 11 12 ;;---------------------------------------------------------------------------------- ;; @@PLEAC@@_2.6 Working with Roman Numerals ;;---------------------------------------------------------------------------------- ;; no roman module available ;;---------------------------------------------------------------------------------- ;; @@PLEAC@@_2.7 Generating Random Numbers ;;---------------------------------------------------------------------------------- (import '(java.util Random)) (def random (Random. )) (def i (+ (.nextInt random (- y (+ x 1))) x)) ;;---------------------------------------------------------------------------------- (def i (+ (.nextInt random 51) 25)) (println i) ;;---------------------------------------------------------------------------------- ;; @@PLEAC@@_2.8 Generating Different Random Numbers ;;---------------------------------------------------------------------------------- ;; Seed the generator with an integer (Random. 5) ;; Use SecureRandom instead to seed with bytes from stdin (import '(java.security SecureRandom)) ;; jli for mbac: unqualified "use" is almost always ungood. use :only ;; or :rename. ;; mbac: fixed! (use '[clojure.contrib.io :only (to-byte-array)]) (SecureRandom. (to-byte-array System/in)) ;; @@PLEAC@@_2.9 Making Numbers Even More Random (let [srng (SecureRandom.) buf (byte-array 10)] (do (.nextBytes srng buf) buf)) ;; @@PLEAC@@_2.10 Generating Biased Random Numbers (def prng (Random.)) (defn gaussian-rand [] (let [[w u1 u2] (loop [] (let [u1 (- (* 2 (.nextDouble prng)) 1) u2 (- (* 2 (.nextDouble prng)) 1) w (+ (* u1 u1) (* u2 u2))] (if (>= w 1) (recur) [w u1 u2]))) w (Math/sqrt (* -2 (/ (Math/log w) w))) g2 (* u1 w) g1 (* u2 w)] g1)) ;; ----------------------------- ;; weight-to-dist: takes a list of pairs mapping key to weight and ;; returns a list of pairs mapping key to probability (defn weight-to-dist [weights] (let [total (apply + (map (fn [[_key weight]] weight) weights))] (map (fn [[key weight]] [key (/ weight total)]) weights))) ;; weighted-rand: takes a list of pairs mapping key to probability ;; and returns the corresponding key (defn weighted-rand [dist] ;; accumulate without mutation (let [go (fn cont [p lst] (if-let [[key weight] (first lst)] (let [pp (- p weight)] (if (< pp 0) [pp key] (recur pp (rest lst)))))) result (go (.nextDouble (Random.)) dist)] ;; to avoid floating point inaccuracies (if (nil? result) (recur dist) (let [[_p key] result] key)))) (def mean 25) (def stddev 2) (def salary (+ (* (gaussian-rand) stddev) mean)) (printf "You have been hired at $%.2f\n" salary) ;; @@PLEAC@@_2.11 Doing Trigonometry in Degrees, not Radians (defn radians [deg] (Math/toRadians deg)) (defn degrees [rad] (Math/toDegrees rad)) (defn sin-deg [deg] (Math/sin (radians deg))) ;; @@PLEAC@@_2.12 Calculating More Trigonometric Functions (defn tan [theta] (/ (Math/sin theta) (Math/cos theta))) ;; or use Math/tan (def y (try (tan (/ Math/PI 2)) (catch Exception e nil))) ;; @@PLEAC@@_2.13 Taking Logarithms (defn log_e [x] (Math/log x)) (defn log_10 [x] (Math/log10 x)) (defn log-base [base value] (/ (log_e value) (log_e base))) (def answer (log-base 10 10000)) (printf "log10(10,000) = %f" answer) ;; @@PLEAC@@_2.14 Multiplying Matrices ;; This is a very academic, purely functional implementation of ;; multiply-matrix. A performance critical implementation would ;; likely not use Clojure's immutable vectors. (defn multiply-matrix [m1 m2] (let [dim (fn [m] [(count m) (count (first m))]) [r1 c1] (dim m1) [r2 c2] (dim m2)] (if (not (= c1 r2)) nil ; matrix dimensions don't match (let [dot-product (fn [v1 v2] (reduce (fn [a i] (+ a (* (nth v1 i) (nth v2 i)))) 0 (range 0 (count v1)))) row (fn [m i] (nth m i)) col (fn [m i] (map (fn [r] (nth (nth m r) i)) (range (count m))))] (map (fn [r] (map (fn [c] (dot-product (row m1 r) (col m2 c))) (range 0 c2))) (range 0 r1)))))) ;; @@PLEAC@@_2.15 Using Complex Numbers ;; c = a * b manually (defrecord Complex [real imag]) (defn mul [a b] (Complex. (* (.real a) (.real b)) (* (.imag a) (.imag b)))) ;; c = a * b using the complex-numbers module (use '(clojure.contrib complex-numbers) '(clojure.contrib.generic [arithmetic :only [+ *]] [math-functions :only [sqrt]])) (def a (complex 3 5)) (def b (complex 2 -2)) (def c (* a b)) (def c (* (complex 3 5) (complex 2 -2))) ; or on one line (defn print-complex-sqrt [x] (let [as-string (fn [c] (format "%s+%si" (real c) (if (= (imag c) 1) "" (imag c))))] (printf "sqrt(%s) = %s\n" (as-string x) (as-string (sqrt x))))) (def d (complex 3 4)) (print-complex-sqrt d) ;; @@PLEAC@@_2.16 Converting Between Octal and Hexadecimal ;; hex and octal should not have leading 0x or 0 characters (defn hex-and-oct-as-decs [hex octal] (let [dec-of-hex (Integer/parseInt hex 16) dec-of-oct (Integer/parseInt octal 8)] ;; use dec-of-hex and dec-of-oct here )) (let [num (-> (do (print "Gimme a number in decimal, octal, or hex: ") (read-line)) .trim Integer/parseInt)] (printf "%s %s %s\n" (Integer/toString num) (Integer/toOctalString num) (Integer/toHexString num))) ;; @@PLEAC@@_2.17 Putting Commas in Numbers (import '(java.text NumberFormat) '(java.util Locale)) (def locale Locale/US) (defn commify-localized [num] (let [nf (NumberFormat/getInstance locale)] (.format nf num))) ;; deck version (defn commify-hipster [numstr] (->> (.toString numstr) reverse (partition-all 3) (interpose \,) flatten reverse (apply str))) ;; @@PLEAC@@_2.18 Printing Correct Plurals (defn print-plurals [hours centuries] (do (printf "It took %d hour%s\n" hours (if (= hours 1) "" "s")) (printf "%d hour%s %s enough.\n" hours (if (= hours 1) "" "s") (if (= hours 1) "is" "are")) (printf "It took %d centur%s\n" centuries (if (= centuries 1) "y" "ies")))) (def noun-rules [[#"ss$" "sses"] [#"ph$" "phes"] [#"sh$" "shes"] [#"ch$" "ches"] [#"z$" "zes"] [#"ff$" "ffs"] [#"f$" "ves"] [#"ey$" "eys"] [#"y$" "ies"] [#"ix$" "ices"] [#"s$" "ses"] [#"x$" "xes"] [#"$" "s"]]) (require '(clojure.contrib [str-utils2 :as s])) (defn noun_plural [word] (some (fn [[re ending]] (if (re-find re word) (s/replace word re ending))) noun-rules)) (def verb_singular noun_plural) ; make function alias ;; Note: there's no perl Lingua::EN::Inflect equivalent module ;; @@PLEAC@@_2.19 Program: Calculating Prime Factors ;; jli for mbac: shouldn't this return {orig 1} for prime numbers? ;; mbac for jli: maybe, but the perl version doesn't do this either (defn get-factors [orig] (letfn [(factor-out [n factors i] (if (zero? (mod n i)) (recur (/ n i) (assoc factors i (inc (factors i 0))) i) [n factors]))] (loop [i 2 sqi 4 [n factors] [orig {}]] (cond (<= sqi n) (recur (inc i) (+ sqi (* 2 i) 1) (factor-out n factors i)) (and (not= n 1) (not= n orig)) (assoc factors n (inc (factors n 0))) :default factors)))) (defn print-factors [orig factors] (let [head (format "%-10d" orig) lines (if (zero? (count factors)) ["PRIME"] (map (fn [[f e]] (format "%d^%d" f e)) factors)) s (apply str (interpose "\n" (cons head lines)))] (println s))) (loop [i 0] (if (< i (count *command-line-args*)) (let [n (Integer/parseInt (.get argv i))] (do (print-factors n (factorize n)) (recur (inc i)))))) ;; @@PLEAC@@_3.0 Introduction ;;------------------------------------ ;; Use a calendar to compute year, month, day, hour, minute and second values. (import '(java.util Calendar)) (import '(java.util GregorianCalendar)) (defn print-day-of-year [] (let [cal (GregorianCalendar.)] (printf "Today is day %d of the current year.\n" (.get cal Calendar/DAY_OF_YEAR)))) ;; @@PLEAC@@_3.1 Finding Today's Date ;;------------------------------------ (import '(java.util Calendar)) (import '(java.util GregorianCalendar)) (defn todays-date [] (let [cal (GregorianCalendar.) day (.get cal Calendar/DATE) month (.get cal Calendar/MONTH) year (.get cal Calendar/YEAR)] [day month year])) (defn print-todays-date [] (let [[day month year] (todays-date)] (printf "The current date is %d %02d %02d\n" year (inc month) day))) ;; @@PLEAC@@_3.2 Converting DMYHMS to Epoch Seconds ;;------------------------------------ (import '(java.util Calendar)) (import '(java.util TimeZone)) (import '(java.util GregorianCalendar)) (defn epoch-seconds-of-dmyhms [tz day month year hour minute second] (let [cal (GregorianCalendar.) zone (TimeZone/getTimeZone tz)] (do (.setTimeZone cal zone) (.set cal Calendar/DAY_OF_MONTH day) (.set cal Calendar/MONTH month) (.set cal Calendar/YEAR year) (.set cal Calendar/HOUR_OF_DAY hour) (.set cal Calendar/MINUTE minute) (.set cal Calendar/SECOND second) (int (/ (.getTime (.getTime cal)) 1000))))) (epoch-seconds-of-dmyhms "UTC" 4 10 2011 12 30 55) ;; @@PLEAC@@_3.3 Converting Epoch Seconds to DMYHMS ;;------------------------------------ (import '(java.util Calendar)) (import '(java.util Date)) (import '(java.util GregorianCalendar)) (defn dmyhms-of-epoch-seconds [seconds] (let [cal (GregorianCalendar.) date (Date. (* seconds 1000))] (do (.setTime cal date) [(.get cal Calendar/DAY_OF_MONTH) (.get cal Calendar/MONTH) (.get cal Calendar/YEAR) (.get cal Calendar/HOUR_OF_DAY) (.get cal Calendar/MINUTE) (.get cal Calendar/SECOND)]))) (defn print-dmyhms-of-epoch-seconds [seconds] (let [[day month year hour minute seconds] (dmyhms-of-epoch-seconds seconds)] (printf "%02d-%02d-%d %02d:%02d:%02d\n" day (inc month) year hour minute seconds))) ;; 3.4 Adding or Subtracting from a Date ;; @@PLEAC@@_4.0 Introduction ;;----------------------------- ;; vectors (def simple ["this" "that" "the" "other"]) (def nested ["this" "that" ["the" "other"]]) (assert (= (count simple) 4)) (assert (= (count nested) 3)) (assert (= (count (nth nested 2)) 2)) ;;----------------------------- (def tune ["The" "Star-Spangled" "Banner"]) ;;----------------------------- ;; @@PLEAC@@_4.1 Specifying a List in Your Program (def a ["quick" "brown" "fox"]) (defn qw "Split string on whitespace. Returns a seq." [s] (seq (.split s "\\s"))) (def a2 (qw "Why are you teasing me?")) (def lines (.replaceAll " The boy stood on the burning deck, It was as hot as glass." "\\ +" "")) ;;----------------------------- (ns bigvector (:require [clojure.string :as str] [clojure.java.io :as io])) (try (let [bigvector (vec (line-seq (io/reader "mydatafile")))] ;; rest of code to do something with bigvector ) (catch java.io.FileNotFoundException e (printf "%s\n" e) (flush) (System/exit 1))) ;;----------------------------- ;; @@PLEAC@@_4.2 Printing a List with Commas ;;----------------------------- (defn commify-series [coll] (case (count coll) 0 "" 1 (first coll) 2 (str/join " and " coll) (str/join ", " (concat (butlast coll) (list (str "and " (last coll))))))) ;;----------------------------- (def array ["red" "yellow" "green"]) (print "I have" array "marbles.\n") ;; Clojure does not have string interpolation. (printf "I have %s marbles.\n" (str/join " " array)) I have [red yellow green] marbles. I have red yellow green marbles. ;;----------------------------- ;; @@PLEAC@@_4.3 Changing Array Size ;;----------------------------- ;; Clojure vectors cannot be modified, but we can create new vectors ;; from existing ones, with differences between the existing and new ;; ones. ;; create smaller array that is a subset of an existing one. Unlike ;; Perl's $#ARRAY = $NEW_LAST_ELEMENT_INDEX_NUMBER, you must use the ;; new number of elements with subvec, which is one larger than the ;; new last element index number. (def newv (subvec v 0 newv-number-of-elements)) ;; In general you can give an arbitrary start (inclusive) and end ;; (exclusive) index to subvec. It only takes O(1) time. The new ;; vector's index i has the same value as the original vector's index ;; (start+i). ;;----------------------------- ;; We can create a new Clojure vector one larger in size than an ;; existing one using assoc or conj. (def newv (assoc v (count v) value)) (def newv (conj v value)) ;; I believe there is no way to expand a vector by an arbitrary amount ;; using a single Clojure built-in function. One could achieve that ;; effect by repeatedly using conj to add individual elements to the ;; end, one at a time, until the desired vector size was reached. ;; However, if you want a sparsely populated array with elements ;; indexed by integer, you are likely to be more satisfied using a map ;; with integer keys than a vector. ;;----------------------------- (defn what-about-that-vector [v] (printf "The vector now has %d elements.\n" (count v)) (printf "The index of the last element is %d.\n" (dec (count v))) (printf "Element #3 is `%s'.\n" (v 3))) ;; Note that qw returns a sequence of elements that is not a Clojure ;; vector. Here we use vec to create a vector containing the same ;; elements as the sequence. (def people (vec (qw "Crosby Stills Nash Young"))) (what-about-that-vector people) ;;----------------------------- The vector now has 4 elements. The index of the last element is 3. Element #3 is `Young'. ;;----------------------------- (def people (pop people)) ;; The following has equivalent behavior for vectors to pop, but not ;; sure if the efficiency is the same. ;;(def people (subvec people 0 (dec (count people)))) (what-about-that-vector people) ;;----------------------------- IndexOutOfBoundsException clojure.lang.PersistentVector.arrayFor (PersistentVector.java:106) The vector now has 3 elements. The index of the last element is 2. ;;----------------------------- ;; As mentioned above, there is no single builtin function to extend a ;; vector by an arbitrarily large number of elements. We'll do it ;; here with a loop. (def people (loop [people people] (if (< (count people) 10001) (recur (conj people nil)) ;; else people))) (what-about-that-vector people) ;;----------------------------- The vector now has 10001 elements. The index of the last element is 10000. Element #3 is `null'. ;;----------------------------- ;; Assigning a value to element 10000 of vector people will not change ;; its size, even if that new value is nil. To make a vector with a ;; smaller size, use subvec or pop as described above. ;; @@PLEAC@@_4.4 Doing Something with Every Element in a List ;;----------------------------- ;; Clojure is often written in a functional style, meaning that you ;; calculate output value from input values. So Clojure's 'for' is ;; actually a way to take one or more input sequences and produce an ;; output sequence, and in fact this is done in a lazy fashion, ;; meaning that no actual computation occurs unless some other code ;; _uses_ elements of the output sequence. ;; If you use Clojure's REPL to try out code before using it in a ;; program, this can easily confuse you, because at the REPL, every ;; expression you enter is read, executed, and the result is printed. ;; The fact that the result is printed often forces lazy expressions ;; to calculate their entire result, but if you use that lazy ;; expression as part of a larger expression or program, it won't be. ;; Here are a couple of quick examples: user=> (range 0 5) (0 1 2 3 4) ;; i iterates over the elements of the sequence (range 0 5). The for ;; expression as a whole returns a sequence containing (inc i) for ;; each input sequence element. It does this lazily, but because we ;; are typing it at the REPL, the output value is used in order to ;; print it. user=> (for [i (range 0 5)] (inc i)) (1 2 3 4 5) ;; Here we add some debug print statements, and its output gets ;; mingled with the printed output value. user=> (for [i (range 0 5)] (do (printf "i=%d\n" i) (inc i))) (i=0 i=1 i=2 i=3 i=4 1 2 3 4 5) ;; Here we assign the value of the for expression to a var. Note that ;; the only output printed is the output value of the def statement, ;; which is #'user/a1. Why don't the printf's get executed? Because ;; the for is lazy, and nothing has used any part of its output value ;; yet. user=> (def a1 (for [i (range 0 5)] (do (printf "i=%d\n" i) (inc i)))) #'user/a1 ;; When we ask for the value of a1, then the output value of the for ;; expression is required, and so its body is executed now. user=> a1 (i=0 i=1 i=2 i=3 i=4 1 2 3 4 5) ;; If you want to force the iteration to occur when it is evaluated, ;; use doseq instead. It does not return any useful value (only nil), ;; and is intended to be used when the body contains side effects. ;; Here the (inc i) is superfluous, since it simply returns a value ;; that is ignored by the rest of the expression around it. user=> (def a1 (doseq [i (range 0 5)] (printf "i=%d\n" i) (inc i))) i=0 i=1 i=2 i=3 i=4 #'user/a1 ;; As mentioned above, doseq always returns nil. user=> a1 nil ;;----------------------------- (doseq [user bad-users] (comlain user)) ;;----------------------------- (doseq [var (sort (keys (System/getenv)))] (printf "%s=%s\n" var (get (System/getenv) var))) ;;----------------------------- (doseq [user all-users] (let [disk-space (get-usage user)] (if (> disk-space MAX-QUOTA) (complain user)))) ;;----------------------------- (require '[clojure.java.shell :as shell]) (doseq [line (str/split (:out (shell/sh "who")) #"\n")] (if (re-find #"tchrist" line) (printf "%s\n" line))) ;;----------------------------- ;; Unlike in Perl, there is nothing in Clojure similar to the $_ and ;; @_ default variables for iterating over lines of an input file or ;; elements of a list. ;; rdr implements interface java.io.BufferedReader in this example, ;; and so can be used with line-seq. (doseq [line (line-seq rdr)] ;; line-seq is a sequence of strings, one for each line in the input ;; file, and they never have a trailing \n (doseq [word (str/split line #"\s+")] (printf "%s" (str/reverse word)))) ;;----------------------------- ;; In Clojure, every for or doseq has variables that are like Perl's ;; "my", i.e. their scope is local to the body of the loop, and any ;; value the symbol had outside the loop is not visible inside, and ;; any change made inside has no affect on the symbol's value outside ;; the loop. (doseq [item array] (printf "i = %s\n" item)) ;;----------------------------- ;; Clojure's native vectors are immutable, so there is no way to ;; modify their elements, although it is easy to create new vectors ;; that are the same as old ones except that a single element has been ;; replaced with a new one. ;; This is a clunky way to do it that loops over the elements of the ;; array explicitly. Note that we first bind the symbol array to the ;; value [1 2 3], then to the value returned by the loop expression. ;; The first value of array is then lost. (let [array [1 2 3] array (loop [a array i 0] (if (< i (count a)) (recur (assoc a i (dec (a i))) (inc i)) a))] (println array)) [0 1 2] ;; A much more functional style for doing this is to use map. Here we ;; use vec on the result of map to convert the list that is returned ;; by map, which is different than a vector in Clojure, to a vector ;; with the same elements. (let [array [1 2 3] array (vec (map dec array))] (println array)) ;; Again, this example achieves a similar effect as the Perl code, but ;; it does not modify a and b in place -- it creates new values and ;; assigns those new values to a and b. (let [a [0.5 3] b [0 1] a (vec (map #(* % 7) a)) b (vec (map #(* % 7) b))] (printf "%s %s\n" a b)) [3.5 21] [0 7] ;; Note that you can use native Java arrays in a straightforward way ;; from Clojure, and these are mutable data structures, like Java's ;; and Perl's mutable arrays. Java arrays cannot be grown or shrunk ;; after creation, except by copying their contents into a new array ;; and abandoning the old one. ;; into-array creates a Java array with values initialized to the ;; elements of a sequence. If you don't give an explicit type for the ;; array elements, they default to the type of the first element of ;; the sequence. I'll create Java arrays of java.lang.Object's below, ;; so that the elements can be a mix of different subclasses of ;; Object. ;; You can use loop, doseq, or dotimes to iterate over the indices of ;; the array. (let [a (into-array Object [0.5 3]) b (into-array Object [0 1])] (dotimes [i (alength a)] (aset a i (* (aget a i) 7))) (dotimes [i (alength b)] (aset b i (* (aget b i) 7))) (printf "%s %s\n" (seq a) (seq b))) ; seq used to create sequence ; of values in arrays a and b (3.5 21) (0 7) ;; Clojure's amap creates new Java arrays, by copying the given one, ;; then iterating over its elements and replacing each one with the ;; result of evaluating a given expression. (let [a (into-array Object [0.5 3]) b (into-array Object [0 1]) a (amap a i temp (* (aget a i) 7)) b (amap b i temp (* (aget b i) 7))] (printf "%s %s\n" (seq a) (seq b))) (3.5 21) (0 7) ;;----------------------------- ;; Because Clojure doesn't provide a way to modify its collections in ;; place, but instead encourages you to create new versions of ;; existing data structures, it doesn't really provide a way to ;; iterate over several different collections in a single loop. ;; clojure.string/trim returns a string the same as the string you ;; give it, except with white space at the beginning and end removed. ;; do-to-map was written by Brian Carper, and he also wrote the ;; explanation for how it works that I have copied below. Original ;; source is at the following URL: ;; http://stackoverflow.com/questions/1638854/clojure-how-do-i-apply-a-function-to-a-subset-of-the-entries-in-a-hash-map ;; It helps to look at it inside-out. In Clojure, hash-maps act like ;; functions; if you call them like a function with a key as an ;; argument, the value associated with that key is returned. So given ;; a single key, the current value for that key can be obtained via: ;; (some-map some-key) ;; We want to take old values, and change them to new values by ;; calling some function f on them. So given a single key, the new ;; value will be: ;; (f (some-map some-key)) ;; We want to associate this new value with this key in our hash-map, ;; "replacing" the old value. This is what assoc does: ;; (assoc some-map some-key (f (some-map some-key))) ;; ("Replace" is in scare-quotes because we're not mutating a single ;; hash-map object; we're returning new, immutable, altered hash-map ;; objects each time we call assoc. This is still fast and efficient ;; in Clojure because hash-maps are persistent and share structure ;; when you assoc them.) ;; We need to repeatedly assoc new values onto our map, one key at a ;; time. So we need some kind of looping construct. What we want is ;; to start with our original hash-map and a single key, and then ;; "update" the value for that key. Then we take that new hash-map ;; and the next key, and "update" the value for that next key. And we ;; repeat this for every key, one at a time, and finally return the ;; hash-map we've "accumulated". This is what reduce does. ;; * The first argument to reduce is a function that takes two ;; arguments: an "accumulator" value, which is the value we keep ;; "updating" over and over; and a single argument used in one ;; iteration to do some of the accumulating. ;; * The second argument to reduce is the initial value passed as the ;; first argument to this fn. ;; * The third argument to reduce is a collection of arguments to be ;; passed as the second argument to this fn, one at a time. ;; So: ;; (reduce fn-to-update-values-in-our-map ;; initial-value-of-our-map ;; collection-of-keys) ;; fn-to-update-values-in-our-map is just the assoc statement from ;; above, wrapped in an anonymous function: ;; (fn [map-so-far some-key] ;; (assoc map-so-far some-key (f (map-so-far some-key)))) ;; So plugging it into reduce: ;; (reduce (fn [map-so-far some-key] ;; (assoc map-so-far some-key (f (map-so-far some-key)))) ;; amap ;; keyseq) ;; In Clojure, there's a shorthand for writing anonymous functions: ;; #(...) is an anonymous fn consisting of a single form, in which %1 ;; is bound to the first argument to the anonymous function, %2 to the ;; second, etc. So our fn from above can be written equivalently as: ;; #(assoc %1 %2 (f (%1 %2))) ;; This gives us: ;; (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq) (defn do-to-map [amap keyseq f] (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)) (let [scalar (str/trim scalar) array (vec (map str/trim array)) ; skip the vec if a list result is OK hash (do-to-map hash (keys hash) str/trim)] ) ;;----------------------------- ;; No foreach/for synonym in Clojure. I believe the existing common ;; alternatives are all mentioned above. ;;----------------------------- ;; @@PLEAC@@_4.5 Iterating Over an Array by Reference ;;----------------------------- ;; Clojure does not have Perl's distinction between an array and an ;; array ref. Clojure lists, vectors, maps, etc. can all contain ;; instances of each other as values, and in maps any of these data ;; structures can be used as keys, too. ;;----------------------------- (def fruits [ "Apple" "Blackberry" ]) (doseq [fruit fruits] (printf "%s tastes good in a pie.\n" fruit)) Apple tastes good in a pie. Blackberry tastes good in a pie. ;;----------------------------- (dotimes [i (count fruits)] (printf "%s tastes good in a pie.\n" (fruits i))) ;;----------------------------- (def namelist { }) (def rogue-cats [ "YellowFang" "BrokenTail" "Clawface" ]) (let [namelist (assoc namelist :felines rogue-cats)] (doseq [cat (namelist :felines)] ; (:felines namelist) gives same result (printf "%s purrs hypnotically..\n" cat))) (printf "--More--\nYou are controlled.\n") ;;----------------------------- (let [namelist (assoc namelist :felines rogue-cats)] (dotimes [i (count (namelist :felines))] (printf "%s purrs hypnotically..\n" ((namelist :felines) i)))) ;;----------------------------- ;; @@PLEAC@@_4.6 Extracting Unique Elements from a List ;;----------------------------- ;; Iterative style -- requires a fair amount of verbiage. ;; TBD: My use of seq/first/rest might be nonstandard here. Is it ;; correct for all cases? If not, what case causes it to break? (loop [seen {} uniq [] l (seq list)] (if-let [item (first l)] (if (not (seen item)) ;; (seen item) is nil if item is not a key ;; in the map seen, and nil is treated as false (recur (assoc seen item 1) (conj uniq item) (rest l)) ;; else (recur seen uniq (rest l))) ;; return a final value from loop statement here, perhaps seen and ;; uniq )) ;; Functional style. If reduce call is confusing, try first reading ;; explanation of do-to-map above. This is a bit simpler than that. (let [seen (reduce #(assoc %1 %2 1) {} list) uniq (vec (keys seen))] ; leave out vec if a list is good enough ;; use seen and/or uniq here ) ;; Clojure also has sets as a built-in data structure. They make it ;; easy to find unique items in a collection. (let [uniq (set list)] ; use (vec (set list)) if you want a vector instead ;; use uniq here ) ;;----------------------------- ;; This is nearly the same as functional style above, except this time ;; we want to count occurrences of items. ;; First we'll define a tiny helper function to increment the entry. ;; In Perl, if you increment an undefined entry in a hash, it treats ;; it as a 0 and increments it to 1. In Clojure, trying to do (inc ;; nil) throws an exception. What we want is a function that when ;; given nil, returns 1, and when given a number, increments it. ;; We'll call it incn. Clojure evaluates all values except false and ;; nil as true, when the value is used as the test in an if ;; expression. (defn incn [x] (if x (inc x) 1)) (let [seen (reduce #(assoc %1 %2 (incn (%1 %2))) {} list) uniq (vec (keys seen))] ; leave out vec if a list is good enough ;; ... ) ;; fnil can help us in cases like the above, when we want a function ;; like inc, except it doesn't work when passed nil. (fnil f ;; default-input) returns a function that works just like f does, ;; except when it is given an argument of nil, it evaluates (f ;; default-input) instead. So incn above is the same as (fnil inc 0). (let [seen (reduce #(assoc %1 %2 ((fnil inc 0) (%1 %2))) {} list) uniq (vec (keys seen))] ; leave out vec if a list is good enough ;; ... ) ;; This expression (assoc map key (f (map key))) is so common that ;; there is a function update-in that can shorten it a bit, as ;; (update-in map [key] f). It can also help update nested maps ;; within maps, but we won't use it for that until later. This ;; generality is the reason that it takes a vector of key values, ;; instead of only a single key value, and that is why the [] are ;; there around key in the call to update-in. (let [seen (reduce #(update-in %1 [%2] (fnil inc 0)) {} list) uniq (vec (keys seen))] ; leave out vec if a list is good enough (printf "seen='%s'\n" seen) ) ;;----------------------------- ;; Here we call function (some-func item) the first time a new item is ;; encountered in the sequence 'list', but never if it is seen a 2nd ;; or larger time later in the list. This function is presumably ;; called for its side effects, since there is no return value being ;; used. (let [seen (reduce #(assoc %1 %2 (if-let [n (%1 %2)] (inc n) (do (some-func %2) 1))) {} list)] ;; ... ) ;;----------------------------- ;; Here the Perl version is closer to the functional style examples ;; given above. No reason to repeat the Clojure code for them here. ;;----------------------------- ;; The Perl code here is very much like a functional style, except its ;; condition mutates the hash 'seen'. I'm not going to try to write a ;; Clojure version that emulates this, since to match its behavior ;; closely would require using a mutable Java hash table. ;; %seen = (); ;; @uniqu = grep { ! $seen{$_} ++ } @list; ;;----------------------------- ;; Here is a functional style version of the Perl code. Let's make a ;; function 'tally' to create a map of occurrence counts of items in a ;; collection. (require '[clojure.string :as str]) (require '[clojure.java.shell :as shell]) (defn tally [coll] (reduce #(update-in %1 [%2] (fnil inc 0)) {} coll)) ;; Note that we use the regex #"\s.*$" as opposed to the one #"\s.*\n" ;; in Perl, because the strings in the sequence lines do not have \n ;; at the end of each one. (let [lines (str/split (:out (shell/sh "who")) #"\n") usernames (map #(str/replace-first % #"\s.*$" "") lines) ucnt (tally usernames) users (sort (keys ucnt))] (printf "users logged in: %s\n" (str/join " " users))) ;; If the count of how many times each username occurred is not ;; important, just the unique ones, then Clojure sets are more ;; straightforward. (let [lines (str/split (:out (shell/sh "who")) #"\n") usernames (map #(str/replace-first % #"\s.*$" "") lines) users (sort (set usernames))] (printf "users logged in: %s\n" (str/join " " users))) ;;----------------------------- ;; @@PLEAC@@_4.7 Finding Elements in One Array but Not Another ;;----------------------------- ;; First we'll do it in a similar style to the Perl version. ;; seen is a map, and we produce a vector aonly. (def A ["a" "b" "c" "d" "c" "b" "a" "e"]) (def B ["b" "c" "d" "c" "b"]) (let [seen (reduce #(assoc %1 %2 1) {} B) aonly (vec (filter #(not (seen %)) A))] ; no vec, if vector not needed (printf "%s\n" (str/join " " aonly))) a a e ;; Then we'll use Clojure sets to simplify it. (require '[clojure.set :as set]) (let [seen (set B) aonly (filter #(not (seen %)) A)] (printf "%s\n" (str/join " " aonly))) a a e ;; We can simplify even further if aonly can be a set of unique ;; elements in A that are not also in B, and the order of the elements ;; does not matter. Note that the original Perl code contains ;; elements of A not also in B in the same order as they occur in A, ;; and if there are duplicates of such elements in A, they will also ;; be duplicated in aonly. (let [aonly (set/difference (set A) (set B))] (printf "%s\n" (str/join " " aonly))) a e ;;----------------------------- ;; I can't think of any direct correspondence in Clojure of the Perl ;; techniques used in this code. The Clojure set examples above are ;; quite concise. ;;----------------------------- ;; This version has a different behavior than the previous one. It ;; adds an item to @aonly at most one time, without duplicates, but ;; the order is the same as the order the items appear in A. ;; If order does not matter, then the set/difference example above is ;; shorter and clearer. ;; If order in aonly does matter, then here is one way to do it. (let [aonly (loop [aonly [] s (seq A) seen #{}] ; If we want to use a previously calculated ; set in seen, replace #{} with seen. (if-let [item (first s)] (if (seen item) (recur aonly (rest s) seen) (recur (conj aonly item) (rest s) (conj seen item))) aonly))] (printf "%s\n" (str/join " " aonly))) ;;----------------------------- (let [hash (assoc hash "key1" 1) hash (assoc hash "key2" 2)] ;; ... ) ;;----------------------------- (let [hash (assoc hash "key1" 1 "key2" 2)] ;; ... ) ;;----------------------------- ;; TBD: What does this Perl code do? ;;----------------------------- ;; TBD: What does this Perl code do? ;;----------------------------- ;; @@PLEAC@@_4.8 Computing Union, Intersection, or Difference of Unique Lists ;;----------------------------- (def a [1 3 5 6 7 8]) (def b [2 3 5 7 9]) ;; All initializations of union, isect, diff will be done in each ;; example below. ;;----------------------------- ;; This time I'll do it using Clojure sets first. (require '[clojure.set :as set]) (let [set-a (set a) ; if a is a non-set collection, create one set-b (set b) union (set/union set-a set-b) isect (set/intersection set-a set-b)] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect))) union=1 2 3 5 6 7 8 9 isect=3 5 7 ;; Now a way more like the style of the Perl examples. ;; ;; Warning: The Perl code has a bug, if the input array b contains ;; duplicates. In this case, the duplicate elements in b will become ;; part of isect, even if the elements are not also in a. The code ;; below emulates this behavior of the Perl examples. The code using ;; Clojure sets above does not. ;; ;; To see this behavior, try out the code below with these values for ;; a and b: ;; ;; (def a [1 3 5 6 7 8]) ;; (def b [2 3 5 7 9 2]) ; 2 is duplicated, and will appear in isect (let [union (reduce #(assoc %1 %2 1) {} a) [union isect] (loop [union union isect {} s (seq b)] (if-let [e (first s)] (recur (assoc union e 1) (if (union e) (assoc isect e 1) isect) (rest s)) ;; return a vector of 2 values from the loop ;; expression, which will be bound to union ;; and isect in the outer let expression. [union isect])) ;; Note that unlike Perl, the map called union becomes ;; inaccessible after the following line. Give the map and ;; sequence different names if you want them both accessible ;; later. union (keys union) isect (keys isect)] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect))) union=9 2 8 7 6 5 3 1 isect=7 5 3 ;;----------------------------- ;; The only way I know to write Clojure code that closely emulates ;; this Perl example is to use thread-local mutable variables, ;; introduced using with-local-vars. These require using var-set to ;; change the value, and var-get to examine the value, which is a bit ;; clunky. (var-get union) can be abbreviated @union, which helps ;; somewhat. If you really want to write something in imperative ;; style, with-local-vars may be your best bet. (let [[union isect] (with-local-vars [union {} isect {}] (doseq [e (seq (concat a b))] ;; The next let statement behaves as Perl's $union{$e}++, ;; incrementing $union{$e}, but returning the value of ;; $union{$e} before the increment occurs. This is nil in ;; Clojure rather than Perl's undef, but both evaluate to ;; false by Clojure 'and' or Perl &&. (and (let [in-union (@union e)] (var-set union (update-in @union [e] (fnil inc 0))) in-union) (var-set isect (update-in @isect [e] (fnil inc 0))))) [@union @isect]) union (keys union) isect (keys isect)] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect))) union=9 2 8 7 6 5 3 1 isect=7 5 3 ;; The example using set/union and set/difference is really the best ;; way to go in Clojure, though. ;;----------------------------- ;; First the clojure.set way: (let [set-a (set a) set-b (set b) union (set/union set-a set-b) isect (set/intersection set-a set-b) diff (set/difference union isect) ;; Or, if you want to find the 'symmetric difference' without ;; explicitly calculation the union and intersection first, ;; another way is: ;; diff (set/union (set/difference set-a set-b) ;; (set/difference set-b set-a)) ] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect)) (printf "diff=%s\n" (str/join " " diff))) union=1 2 3 5 6 7 8 9 isect=3 5 7 diff=1 2 6 8 9 ;; Next a way closer to the Perl code, but without with-local-vars. ;; Function tally copied from an earlier example, repeated for easier ;; reference. (defn tally [coll] (reduce #(update-in %1 [%2] (fnil inc 0)) {} coll)) (let [count (tally (concat a b)) [union isect diff] (loop [union [] isect [] diff [] s (keys count)] (if-let [e (first s)] (if (== (count e) 2) (recur (conj union e) (conj isect e) diff (rest s)) (recur (conj union e) isect (conj diff e) (rest s))) [union isect diff]))] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect)) (printf "diff=%s\n" (str/join " " diff))) union=9 2 8 7 6 5 3 1 isect=7 5 3 diff=9 2 8 6 1 ;;----------------------------- ;; A similar trick as used in the Perl example can be made to work ;; with Clojure local mutable vars, if you really want to do it. (let [count (tally (concat a b)) [union isect diff] (with-local-vars [union [] isect [] diff []] (doseq [e (keys count)] (var-set union (conj (var-get union) e)) (let [target (if (== (count e) 2) isect diff)] (var-set target (conj (var-get target) e)))) [(var-get union) (var-get isect) (var-get diff)])] (printf "union=%s\n" (str/join " " union)) (printf "isect=%s\n" (str/join " " isect)) (printf "diff=%s\n" (str/join " " diff))) union=9 2 8 7 6 5 3 1 isect=7 5 3 diff=9 2 8 6 1 ;;----------------------------- ;; @@PLEAC@@_4.9 Appending One Array to Another ;;----------------------------- ;; No vec call needed if the result can be a list. (let [ARRAY1 (vec (concat ARRAY1 ARRAY2))] ;; ... ) ;;----------------------------- ;; I don't know of any Clojure code that looks like that in the Perl ;; example for combining two lists. The example above using concat ;; will do the job. ;;----------------------------- (def members ["Time" "Flies"]) (def initiates ["An" "Arrow"]) (let [members (vec (concat members initiates))] ;; members is now ["Time" "Flies" "An" "Arrow"] ) ;;----------------------------- ;; Clojure data structures are immutable, so we can't write a splice ;; function that modifies these data structures, but we can write a ;; split that will return a new data structure that is similar in its ;; value to the Perl one, after splice modifies it. ;; First, we'll write a simpler version that only works with an offset ;; in the range [0, n] where n is the length of the vector, and a ;; non-negative length such that offset+length is also in the range ;; [0,n]. (defn splice [v offset length coll-to-insert] (vec (concat (subvec v 0 offset) coll-to-insert (subvec v (+ offset length))))) ;; Since the example below uses a splice with negative offset, I'll go ;; ahead and give what I think is a full implementation of all cases ;; of positive, 0, or negaitve arguments to Perl's splice (and also ;; substr) for offset and length. This helper function converts Perl ;; offset and length arguments to Clojure start and end arguments for ;; subvec (and also subs). It is a bit of a mess because of all of ;; the conditions to check. There is likely code much like this ;; buried inside of Perl's implementation of subs and splice. (defn ps-start-end ([n offset] (cond (neg? offset) [(max 0 (+ n offset)) n] (> offset n) nil :else [offset n])) ([n offset c] (let [start (if (neg? offset) (+ n offset) offset) end (if (neg? c) (+ n c) (+ start c))] (cond (neg? start) (if (neg? end) nil [0 (min n end)]) (> start n) nil :else [start (min n (max start end))])))) ;; Here we implement splice that takes a vector and either just an ;; offset, an offset and a length, or offset, length, and collection ;; of items to splice in. (defn splice-helper [v start end coll-to-insert] (vec (concat (subvec v 0 start) coll-to-insert (subvec v end)))) (defn splice ([v offset] (when-let [[start end] (ps-start-end (count v) offset)] (splice-helper v start end []))) ([v offset length] (splice v offset length [])) ([v offset length coll-to-insert] (when-let [[start end] (ps-start-end (count v) offset length)] (splice-helper v start end coll-to-insert)))) (let [members (splice members 2 0 (cons "Like" (seq initiates))) _ (printf "%s\n" (str/join " " members)) members (splice members 0 1 ["Fruit"]) members (splice members -2 2 ["A" "Banana"]) ] (printf "%s\n" (str/join " " members))) ;;----------------------------- Time Flies Like An Arrow Fruit Flies Like A Banana ;;----------------------------- ;; @@PLEAC@@_4.10 Reversing an Array ;;----------------------------- (def reversed (vec (reverse array))) ; remove vec call if sequence result OK ;;----------------------------- (loop [i (dec (count ARRAY))] (when (>= i 0) ;; do something with (ARRAY i) (recur (dec i)))) ;; alternate version (doseq [i (range (dec (count ARRAY)) -1 -1)] ;; do something with (ARRAY i) ) ;;----------------------------- (def ascending (sort users)) ;; If you want to make the comparison function explicit, you can use ;; this, which is equivalent to the above. (def ascending (sort #(compare %1 %2) users)) (def descending (reverse ascending)) ;; one-step: sort with reverse comparison (def descending (sort #(compare %2 %1) users)) ;;----------------------------- ;; @@PLEAC@@_4.11 Processing Multiple Elements of an Array ;;----------------------------- ;; The Perl code @FRONT = splice(@ARRAY, 0, $N); has the side effect ;; of modifying @ARRAY, removing the first $N elements from it, and ;; simultaneously assigning an array of those first $N elements to ;; @FRONT. ;; To get a similar effect in Clojure can be done as follows: (let [FRONT (subvec array 0 n) array (subvec array n)] ;; ... ) ;; The Perl code @END = splice(@ARRAY, -$N); also has two side ;; effects: removing the last $N elements from @ARRAY, and assigning ;; an array containing those last $N elements to @END. (let [END (subvec array (- (count array) n)) array (subvec array 0 (- (count array) n))] ;; ... ) ;; or if you want to use the Clojure splice function defined earlier: (def array [1 2 3 4 5 6 7 8 9]) (def n 4) (let [END (splice array 0 (- n)) array (splice array (- n))] (printf "array=%s END=%s\n" (str/join " " array) (str/join " " END)) ;; ... ) ;;----------------------------- ;; It is not really possible to write a Clojure function that mutates ;; an immutable data structure, like Perl's shift2 and pop2 do. ;; We could write a function that takes a vector v and return a vector ;; of the two things: (1) a vector of the first two elements of v, and ;; (2) a vector of all but the first two elements of v. The caller of ;; that function could then choose to use one or both of those values ;; and bind them to symbols of its choice. (defn shift2 [v] [(subvec v 0 2) (subvec v 2)]) (defn pop2 [v] (let [i (- (count v) 2)] [(subvec v i) (subvec v 0 i)])) ;;----------------------------- ;; Clojure qw defined far above. Reuse it here. (def friends (vec (qw "Peter Paul Mary Jim Tim"))) (let [[[this that] friends] (shift2 friends)] ;; this contains "Peter", that has "Paul", and friends has "Mary", ;; "Jim", and "Tim". (printf "this=%s that=%s friends=%s\n" this that (str/join " " friends))) (def beverages (vec (qw "Dew Jolt Cola Sprite Fresca"))) (let [[pair beverages] (pop2 beverages)] ;; (pair 0) contains Sprite, (pair 1) has Fresca, and beverages has ;; ["Dew" "Jolt" "Cola"] (printf "(pair 0)=%s (pair 1)=%s beverages=%s\n" (pair 0) (pair 1) (str/join " " beverages))) ;;----------------------------- ;; Clojure doesn't have references exactly like Perl's. It does have ;; something called refs, but in Clojure they are intended primarily ;; for handling coordinated concurrent changes to multiple refs with ;; transactions. ;; ;; TBD: It isn't clear to me whether Clojure has a way to implement ;; the behavior in this Perl code. ;; ;; $line[5] = \@list; ;; @got = pop2( @{ $line[5] } ); ;;----------------------------- ;; @@PLEAC@@_4.12 Finding the First List Element That Passes a Test ;;----------------------------- ;; If we know that the matching item cannot possibly have a value that ;; Clojure would evaluate as false, i.e. false or nil, then we can ;; write the following. Note that filter is lazy, so since we only ;; ask for the first element, the rest of the elements after the ;; first, if any, will not be computed. (Exception: If array is a ;; chunked sequence, the predicate function will be evaluated on all ;; elements of array in the same chunk as the first one that returns a ;; logical true value.) (def array [1 3 5 7 9 11 12 13 15]) (def array [1 3 5 7 9 11 13 15]) (if-let [match (first (filter even? array))] (printf "Found matching item %s\n" match) (printf "No matching item found\n")) ;; Be careful! That code will do the wrong thing if the value false ;; or nil is in the array, and the criterion we are checking for is ;; true for such a value. For example: (def array [1 3 5 7 9 11 nil 13 15]) (if-let [match (first (filter nil? array))] (printf "Found matching item %s\n" match) (printf "No matching item found\n")) No matching item found ;; In this case, nil? returned true for the value nil in array, so ;; filter did include nil in its output sequence. However, this ;; causes first to return the first element of the sequence, nil. if ;; and if-let treat nil as false, and thus does the else case. ;; If we want to avoid that possibility, we can use the function some, ;; and have a predicate function that returns something besides ;; nil/false in the matching case. One way to do that is to bundle up ;; the matching value in a vector, because if and if-let treat [nil] ;; and [false] as true. After all, they are not the same as the ;; values nil or false. (def array [1 3 5 7 9 11 nil 13 15]) (if-let [[match] (some #(if (nil? %) [%]) array)] (printf "Found matching item %s\n" match) (printf "No matching item found\n")) Found matching item null ;;----------------------------- ;; The previous examples will work, of course. If you want the index ;; of the matching element, though, they won't do. We could do it ;; with loop. Note that this works with vectors, but not with other ;; collections, whereas earlier examples work with all kinds of ;; collections. (let [match-idx (loop [i 0] (if (< i (count array)) (if (even? (array i)) i (recur (inc i))) nil))] (if match-idx (printf "Found matching item %s\n" (array match-idx)) (printf "No matching item found\n"))) ;;----------------------------- ;; TBD: Read more about what this example is intended to do. Should ;; Clojure example create a new class? Seems like overkill. ;;----------------------------- ;; Clojure loops like loop, dotimes, doseq all bind symbols locally, ;; i.e. within their body, only. They do not have any accessible ;; value after the body of the loop is complete. If you really want ;; to do it in the way the Perl code is written, it seems you must ;; write imperative style code, like this. (with-local-vars [i 0] (loop [] (if (< @i (count array)) (if (even? (array @i)) true ; stop the loop (do (var-set i (inc @i)) (recur))))) (if (< @i (count array)) (printf "Found matching item %s\n" (array @i)) (printf "No matching item found\n"))) ;; Another way is almost exactly like one of the examples above, where ;; you return the loop index as the value of the loop expression. (let [i (loop [i 0] (if (< i (count array)) (if (even? (array i)) i (recur (inc i))) i))] (if (< i (count array)) (printf "Found matching item %s\n" (array i)) (printf "No matching item found\n"))) ;;----------------------------- ;; @@PLEAC@@_4.13 Finding All Elements in an Array Matching Certain Criteria ;;----------------------------- (def matching (filter #(test %) collection)) ;;----------------------------- ;; You can write Clojure code that works much like this Perl code, but ;; filter is shorter to write in Clojure, much like grep is in Perl. (let [matching (loop [matching [] s collection] (if-let [s (seq s)] (if (test (first s)) (recur (conj matching (first s)) (next s)) (recur matching (next s))) matching))] (printf "matching=%s\n" (str/join " " matching))) ;;----------------------------- (def nums [5 1000000 1000001 -2]) (def bigs (filter #(> % 1000000) nums)) (def bigs (filter #(> (users %) 10000000) (keys users))) ;;----------------------------- (def matching (filter #(re-find #"^gnat " %) (str/split (:out (shell/sh "who")) #"\n"))) ;;----------------------------- ;; Just calling function position on elements of employees, not ;; treating them as objects. (def engineers (filter #(= (position %) "Engineer") employees)) ;;----------------------------- (def secondary-assistance (filter #(and (>= (income %) 26000) (< (income %) 30000)) applicants)) ;;----------------------------- ;; @@PLEAC@@_4.14 Sorting an Array Numerically ;;----------------------------- (def sorted (sort unsorted)) ;; Or if you want to do an explicit comparison function: (def sorted (sort #(compare. %1 %2) unsorted)) ;; Note that Clojure does not have the distinction between Perl's <=> ;; for comparing scalars as numbers vs. cmp for comparing scalars as ;; strings, because Clojure does not auto-convert value between types ;; the way Perl does. ;;----------------------------- (require '[clojure.java.shell :as shell]) (doseq [pid (sort pids)] (printf "%d\n" pid)) (printf "Select a process ID to kill:\n") (flush) ; println does an automatic flush, but printf does not (let [pid (read-line)] ;; Note that re-matches only returns true if the whole string ;; matches the regexp. It is the same as (re-find #"^\d+$" pid). (when (not (re-matches #"\d+" pid)) (printf "Exiting...\n") ; prints to *out*, which is likely not stderr (flush) (System/exit 1)) ;; TBD: Is there a 'platform-independent' API for killing a process ;; available from Clojure or Java? If so, use it here. The ;; following depends upon a process named "kill" being available on ;; the system, so probably won't work on Windows, whereas Perl's ;; subroutine kill would. (shell/sh "kill" "-TERM" (str pid)) ;; TBD: Similar question for sleep. I'm almost sure Java must have ;; something here. (shell/sh "sleep" "2") (shell/sh "kill" "-KILL" (str pid))) (System/exit 0) ;;----------------------------- (def descending (sort #(compare. %2 %1) unsorted)) ;;----------------------------- ;; TBD: Put sort function in separate Clojure namespace ;;----------------------------- (def all (sort #(compare. %2 %1) [4 19 8 3])) ;;----------------------------- ;; @@PLEAC@@_5.0 Introduction (ns pleac-section-5 (:require [clojure.java.io :as io] [clojure.string :as string])) ;; The commas are whitespace and optional. Maps are printed with them ;; at the REPL for readability. (def age {"Nat" 24, "Jules" 25, "Josh" 17}) (def age (assoc age "Nat" 24)) (def age (assoc age "Jules" 25)) (def age (assoc age "Josh" 17)) ;; Commas omitted here. (def food-color {"Apple" "red" "Banana" "yellow" "Lemon" "yellow" "Carrot" "orange"}) ;; @@PLEAC@@_5.1 Adding an Element to a Hash ;; Maps, like all core Clojure data structures, are immutable. ;; Functions for "changing" maps just return new maps. (def food-color (assoc food-color "Raspberry" "pink")) (println "Known foods:") (doseq [food (keys food-color)] (println food)) ;; Known foods: ;; Carrot ;; Banana ;; Raspberry ;; Lemon ;; Apple ;; @@PLEAC@@_5.2 Testing for the Presence of a Key in a Hash (if (contains? food-color "key") "exists" "doesn't exist") (doseq [name ["Banana" "Martini"]] (if (contains? food-color name) (println name "is a food.") (println name "is a drink."))) ;; Banana is a food. ;; Martini is a drink. (def age {}) (def age (assoc age "Toddler" 3)) (def age (assoc age "Unborn" 0)) (def age (assoc age "Phantasm" nil)) ;; Maps are functions from keys to values, and can be called exactly ;; like functions, taking a key as an argument. The function call ;; returns nil if the key doesn't exist. This works just like using ;; the function "get". (doseq [thing ["Toddler" "Unborn" "Phantasm" "Relic"]] (printf "%s: " thing) (when (contains? age thing) (print "Exists ")) ;; get returns nil when the key isn't in the map, and when the key ;; does exist and the value is nil. (when (get age thing) (print "Defined ")) ;; This works just like the above. Output differs from Perl because ;; 0 is not falsy. (when (age thing) (print "True ")) (newline)) ;; Toddler: Exists Defined True ;; Unborn: Exists Defined True ;; Phantasm: Exists ;; Relic: (defn file-sizes [files] (reduce (fn [map file] (let [file (.trim file)] (if (contains? map file) map (assoc map file (.length (java.io.File. file)))))) {} files)) (file-sizes (line-seq (io/reader *in*))) ;; @@PLEAC@@_5.3 Deleting from a Hash ;; dissoc is used to "remove" (return a new map without the key) (defn print-foods [] (println "Keys:" (string/join " " (keys food-color))) (print "Values: ") (doseq [food (keys food-color)] (if (food-color food) (printf "%s " (food-color food)) (print "(undef) "))) (newline)) (println "Initially:") (print-foods) (println "\nWith Banana undef") (def food-color (assoc food-color "Banana" nil)) (print-foods) (println "\nWith Banana deleted") (def food-color (dissoc food-color "Banana")) (print-foods) ;; Initially: ;; Keys: Carrot Banana Lemon Apple ;; Values: orange yellow yellow red ;; With Banana undef ;; Keys: Carrot Banana Lemon Apple ;; Values: orange (undef) yellow red ;; With Banana deleted ;; Keys: Carrot Lemon Apple ;; Values: orange yellow red (def food-color (dissoc food-color "Banana" "Apple" "Cabbage")) ;; @@PLEAC@@_5.4 Traversing a Hash (doseq [[key value] food-color] ;; do something ) (doseq [key (keys food-color)] (let [value (food-color key)] ;; do something )) ;; food-color per the introduction (doseq [[food color] food-color] (printf "%s is %s.\n" food color)) ;; Carrot is orange. ;; Banana is yellow. ;; Lemon is yellow. ;; Apple is red. (doseq [food (keys food-color)] (let [color (food-color food)] (printf "%s is %s.\n" food color))) ;; Carrot is orange. ;; Banana is yellow. ;; Lemon is yellow. ;; Apple is red. (doseq [food (sort (keys food-color))] (let [color (food-color food)] (printf "%s is %s.\n" food color))) ;; Apple is red. ;; Banana is yellow. ;; Carrot is orange. ;; Lemon is yellow. ;; There isn't an idiomatic way to reset an iteration through a ;; collection in Clojure. (defn countfrom [file] (let [lines (line-seq (io/reader file)) match-sender (fn [line] (second (re-matches #"^From: (.*)" line))) from (reduce (fn [map line] (let [sender (match-sender line) cur (get map sender 0)] (assoc map sender (inc cur)))) {} lines)] (doseq [[person n] (sort from)] (printf "%s: %d\n" person n)))) ;; FILE ACCESS ;; @@PLEAC@@_7.0 Introduction ;; ----------------------------- (ns pleac-section-7.0 (:require [clojure.java.io :as io]) (:import [java.io BufferedReader FileReader])) ;; The perl version contains an or die "Couldn't open filename: $!" ;; after the file open, but this isn't quite as necessary ;; in languages with exceptions, such as Clojure. (defn print-blue-lines-in-file [filename] (with-open [reader (io/reader filename)] (doseq [line (line-seq reader)] (if (.contains line "blue") (println line))))) ;; => (print-blue-lines-in-file "/usr/local/widgets/data") ;; blue ;; blue's ;; bluebell ;; ... ;; ----------------------------- ;; The Perl code shows how to use * to cast the STDIN file handle ;; to a scalar variable. This is unnecessary in Clojure, the ;; stdin reader object is already bound to the name *in*, which ;; can be manipulated like any other symbol. (def stdin-var *in*) (mysub stdin-var logfile) ;; ----------------------------- ;; Here's another way to do the blue line iterator. (defn print-blue-lines-in-file [filename] (let [compiled-regex #"blue"] (with-open [rdr (io/BufferedReader. (io/FileReader. filename))] (doseq [line (line-seq rdr)] (when-not (re-find compiled-regex line) (printf "%s\n" line)))))) (defn print-digital-lines-from-stdin [] (let [digit-regex "#\d"] (with-open [reader (io/reader *in*)] (doseq [line (line-seq reader)] (if (re-find digit-regex) (printf "Read: %s\n" line) (println *err* "No digit found.")))))) ;; ----------------------------- ;; The Perl code shows how to assign a file handle to LOGFILE... (def log-file-handle (io/writer "/tmp/log" :append true)) ;; ----------------------------- (.close log-file-handle) ;; ----------------------------- ;; ... but because unlike Perl, flow-control in Clojure can be ;; interupted with conditions and exceptions, it's a good idea to ;; use the with-open macro to ensure the file handle is closed. (with-open [log-file-handle (io/writer "/tmp/log" :append true)] ;; do stuff with log-file-handle. Log-file-handle wlll be closed when ;; evaluation of this expression terminates. ) ;; ----------------------------- ;; The with-out-str macro redirects everything written to *out* ;; into a string. ;; mbac: rewrite as a macro so calling is less awkward. (defn append-stdout-to-file [f filename] (with-open [fh (io/writer filename :append true)] (.write fh (with-out-str (f))))) ;; => (append-stdout-to-file (fn [] (printf "foo bar baz\n")) "/tmp/log.txt") ;; @@PLEAC@@_7.1 Opening a File ;; ----------------------------- (ns pleac-section-7.1 (:require [clojure.java.io :as io]) (:import [java.io BufferedReader FileReader])) ;; open PATH for reading (def source (io/reader path)) ;; open PATH for writing (def sink (io/writer path)) ;; ---------------------------- ;; The Perl code makes POSIX open for read and open for write calls ;; which aren't directly accessible from Clojure. ;; mbac: maybe find a UNIX/POSIX module? ;; ----------------------------- ;; The Perl code shows how to open files through an object oriented ;; interface to contrast the file handle interface. Clojure already uses ;; objects. ;; mbac: maybe we can show interesting file openers from contrib instead? ;; ----------------------------- ;; sysopen(FILEHANDLE, $name, $flags) or die "Can't open $name : $!"; ;; sysopen(FILEHANDLE, $name, $flags, $perms) or die "Can't open $name : $!"; ;; #----------------------------- ;; open(FH, "< $path") or die $!; ;; sysopen(FH, $path, O_RDONLY) or die $!; ;; #----------------------------- ;; open(FH, "> $path") or die $!; ;; sysopen(FH, $path, O_WRONLY|O_TRUNC|O_CREAT) or die $!; ;; sysopen(FH, $path, O_WRONLY|O_TRUNC|O_CREAT, 0600) or die $!; ;; #----------------------------- ;; sysopen(FH, $path, O_WRONLY|O_EXCL|O_CREAT) or die $!; ;; sysopen(FH, $path, O_WRONLY|O_EXCL|O_CREAT, 0600) or die $!; ;; #----------------------------- ;; open(FH, ">> $path") or die $!; ;; sysopen(FH, $path, O_WRONLY|O_APPEND|O_CREAT) or die $!; ;; sysopen(FH, $path, O_WRONLY|O_APPEND|O_CREAT, 0600) or die $!; ;; #----------------------------- ;; sysopen(FH, $path, O_WRONLY|O_APPEND) or die $!; ;; #----------------------------- ;; open(FH, "+< $path") or die $!; ;; sysopen(FH, $path, O_RDWR) or die $!; ;; #----------------------------- ;; sysopen(FH, $path, O_RDWR|O_CREAT) or die $!; ;; sysopen(FH, $path, O_RDWR|O_CREAT, 0600) or die $!; ;; #----------------------------- ;; sysopen(FH, $path, O_RDWR|O_EXCL|O_CREAT) or die $!; ;; sysopen(FH, $path, O_RDWR|O_EXCL|O_CREAT, 0600) or die $!; ;; #----------------------------- @@INCOMPLETE@@