Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ;;; -*- mode: Clojure; coding: utf-8 -*-
- ;;; Solution by Luis Sergio Oliveira for the Alien Numbers exercise from
- ;;; Programming Praxis
- ;;; See http://programmingpraxis.com/2010/09/24/alien-numbers/
- ;;; Run from the shell as so:
- ;;; ~/programacao/programmingpraxis$ ../clojure/clj-dev.sh alien-numbers.clj <<-END
- ;;; > 2
- ;;; > abc abcd 0123456789
- ;;; > ([}} {([}? 0123456
- ;;; > END
- ;;;
- ;;; Or execute its tests like so:
- ;;; (clojure.test/run-tests 'euluis.aliennum)
- (ns euluis.aliennum
- (:use [clojure.test :only [is are testing deftest]]))
- ^L
- ;;; Conversion given strings alien-num source-lang target-lang.
- ;;; See convert-num fn which is the entry point.
- (defn decimal-to-lang
- "Converts decimal-num to the equivalent number in lang as a string.
- The algorithm was a translation of the algorithm by Rodrigo Menezes
- in C# that he posted in the programming praxis site."
- [decimal-num lang]
- (let [lang-radix (count lang)]
- (loop [decimal-value decimal-num lang-num []]
- (if (>= 0 decimal-value)
- (apply str lang-num)
- (recur (int (/ decimal-value lang-radix))
- (concat [(nth lang (mod decimal-value lang-radix))] lang-num))))))
- (deftest decimal-to-lang-test
- (testing "that given a decimal-num and a target lang, decimal-to-lang fn
- converts it correctly to expected-num"
- (are [expected-num decimal-num lang]
- (= expected-num (decimal-to-lang decimal-num lang))
- "1100120" 987 "012"
- "33123" 987 "0123"
- "3" 3 "0123456789")))
- (defn digit-index
- [digit lang]
- (loop [i 0]
- (if (= digit (nth lang i))
- i
- (recur (inc i)))))
- (defn lang-to-decimal
- [alien-num lang]
- (let [radix (count lang)
- ralien-num (reverse alien-num)]
- (loop [i 0 decimal-num 0 product 1]
- (if (= i (count ralien-num))
- decimal-num
- (recur (inc i) (+ decimal-num (* (digit-index (nth ralien-num i) lang) product))
- (* radix product))))))
- (deftest lang-to-decimal-test
- (testing "that given an alien-num and a lang we can transform it to a decimal number"
- (are [expected-num alien-num lang]
- (= expected-num (lang-to-decimal alien-num lang))
- 987 "1100120" "012"
- 987 "33123" "0123"
- 3 "11" "01")))
- (defn convert-num
- "Convert alien-num which is in source-lang into the same number in target-lang"
- [alien-num source-lang target-lang]
- (decimal-to-lang (lang-to-decimal alien-num source-lang) target-lang))
- (deftest convert-num-tests
- (testing "convert-num tests based on 2 to 36 radix numerals"
- (are [expected-num alien-num source-lang target-lang]
- (= expected-num
- (convert-num alien-num source-lang target-lang))
- ;; test: 987 0123456789 012 -> should be 1100120
- "1100120"
- "987" "0123456789" "012"
- "987"
- "1100120" "012" "0123456789"
- "33123"
- "987" "0123456789" "0123"
- "//-*/"
- "987" "0123456789" "+-*/"
- "RF"
- "987" "0123456789" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "35"
- "Z" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"
- "cj"
- "\"!" "!\"#$%&'()*+,-./:;?@[\\]^_`{|}~" "abcdefghij"
- "3"
- "11" "01" "0123456789")))
- ^L
- ;;; Process the I/O
- ;;; N
- ;;; "<alien number>" "<sl0>..<slN>" "<tl0>..<tlM>"
- ;; production code to retrieve the lines containing the numbers to convert
- (defn my-read-line
- ([] (read-line))
- ([stream]
- (binding [*in* stream]
- (read-line))))
- (defn get-lines
- "Processes stream, returning a seq of lines in the order they are given
- and each line is guaranteed to be a string."
- [stream]
- (let [lines-number (Integer. (my-read-line stream))
- lines (loop [n lines-number lines '()]
- (if (>= 0 n)
- lines
- (recur (dec n) (conj lines (my-read-line stream)))))]
- ;; what should be the behavior when we inconsistent number and lines?
- ;; I'll go for beneficent behavior...
- (reverse (filter #(not (nil? %)) lines))))
- ;; test code to retrieve the lines containing the numbers to convert
- (defn stream-str
- "Returns an open BufferedReader from str."
- [str]
- (java.io.BufferedReader. (java.io.StringReader. str)))
- (defn get-lines-str
- "Given str, creates and opens a stream from this and hands over to get-lines."
- [str]
- (with-open [s (stream-str str)]
- (get-lines s)))
- (deftest get-lines-tests
- (testing "get-lines for happy path test cases"
- (are [expected-lines in-str]
- (= expected-lines (get-lines-str in-str))
- ["123 abc" "4 5 7"] "2\n123 abc\n4 5 7\n"
- ;; No \\n in the end, but, get-lines has benevolent behavior.
- ["1" "2" "3"] "3\n1\n2\n3"
- ;; Although the specified number of lines is larger than the actual
- ;; lines, get-lines has benevolent behavior.
- ["1" "2" "3"] "4\n1\n2\n3"
- [] "0"
- [] "-1\n33\n44"))
- (testing "get-lines for exceptional cases"
- (is (thrown? NullPointerException (get-lines nil)))
- (are [str]
- (thrown? NumberFormatException (get-lines-str str))
- "ab1\n line 1"
- "1 not valid\n1")))
- ^L
- ;; transform each line to convert, validating it and making it amiable for conversion
- ;; 0-9a-zA-Z!"#$%&'()*+,-./:;?@[\]^_`{|}~
- (def valid-chars (set (concat "!\"#$%&'()*+,-./:;?@[\\]^_`{|}~"
- (map char (range (int \0) (inc (int \9))))
- (map char (range (int \a) (inc (int \z))))
- (map char (range (int \A) (inc (int \Z)))))))
- (defn throw-illegal-arg
- "Throws IllegalArgumentException constructed with s."
- [s]
- (throw (IllegalArgumentException. s)))
- (defn if-line-invalid-throw
- "Checks if line-data is valid, throwing an IllegalArgumentException if it
- isn't."
- [line-data original-line]
- (if (not (= 3 (count line-data)))
- (throw-illegal-arg
- (str "Expected \"<alien-num> <source-lang> <target-lang>\", but, was: "
- original-line))
- (let [alien-num (first line-data)
- source-lang (second line-data)
- target-lang (nth line-data 2)]
- (cond
- (= (first alien-num) (first source-lang))
- (throw-illegal-arg
- "First digit of alien-num is the lowest valued digit of the source-lang.")
- (not (every? (set source-lang) alien-num))
- (throw-illegal-arg
- (str "Not all digits of alien-num (" alien-num
- ") are contained in the source-lang (" source-lang ")."))
- (not (= (count (set source-lang)) (count source-lang)))
- (throw-illegal-arg (str "Not all digits of the source-lang ("
- source-lang ") are unique."))
- (not (= (count (set target-lang)) (count target-lang)))
- (throw-illegal-arg (str "Not all digits of the target-lang ("
- target-lang ") are unique."))
- ;; 0-9a-zA-Z!"#$%&'()*+,-./:;?@[\]^_`{|}~
- (not (every? valid-chars (set (concat alien-num source-lang target-lang))))
- (throw-illegal-arg (str "There are invalid chars in "
- original-line "."))))))
- (defn process-line [s]
- "Being s an input line, transform and validate it."
- (let [split-chars #{\space \tab}
- line-data (remove #(some split-chars %)
- (partition-by #(contains? split-chars %) s))]
- (if-line-invalid-throw line-data s)
- (map #(apply str %) line-data)))
- ;; tests for the transformation and validation functions
- (deftest process-line-tests
- (testing "that given well formed lines the result is correct"
- (are [expected line]
- (= expected (process-line line))
- ["123" "0123456789" "abcdefghij"]
- "123 0123456789 abcdefghij"
- ;; notice that the benevolent behavior is guaranteed by accepting
- ;; a tab character and one space
- ["1" "01" "abc"]
- (str "1 01" \tab " abc")))
- (testing "that invalid input is rejected"
- (are [line]
- (thrown? IllegalArgumentException (process-line line))
- "1 0123 ABC xy" ; too many arguments
- "123 012345678" ; too few arguments
- ;; the number should not start with the lowest digit of the source language
- "012 01234 ABC"
- ;; the source language doesn't contain all digits in the number
- "12x 0123 xyz0"
- "12x x122 YZXO" ; the source language contains duplicate digits
- "12x x123 YZXY" ; the target language contains duplicate digits
- ;; the target language contains the invalid digit €
- "12x x123 €ZXY")))
- ^L
- ;; output
- (defn case-out
- [case-num converted-num]
- (str "Case #" case-num ": " converted-num))
- (deftest case-out-tests
- (is (= "Case #3: xyz!!"
- (case-out 3 "xyz!!"))
- "given a case-num and a converted-num a correct str is returned")
- (is (= "Case #55: java.lang.IllegalArgumentException: There are invalid chars in 12x x123 €ZXY."
- (case-out 55 (IllegalArgumentException. "There are invalid chars in 12x x123 €ZXY.")))))
- ^L
- ;; the entry point
- (defn main [args]
- (let [lines (get-lines *in*)
- numbered-lines (map-indexed (fn [case-num line] [(inc case-num) line]) lines)
- cases (map (fn [[case-num line]]
- [case-num
- (try (->> (process-line line)
- (apply convert-num))
- (catch IllegalArgumentException e e))])
- numbered-lines)]
- (doseq [[case-num result] cases]
- (println (case-out case-num result)))))
- (main *command-line-args*)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement