Check out the Pastebin Gadgets Shop. We have thousands of fun, geeky & affordable gadgets on sale :-)Want more features on Pastebin? Sign Up, it's FREE!
tweet

# Alien Numbers in Clojure

By: a guest on Oct 26th, 2010  |  syntax: Lisp  |  size: 8.83 KB  |  views: 329  |  expires: Never
This paste has a previous version, view the difference. Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
1. ;;; -*- mode: Clojure; coding: utf-8 -*-
2. ;;; Solution by Luis Sergio Oliveira for the Alien Numbers exercise from
3. ;;; Programming Praxis
4. ;;; See http://programmingpraxis.com/2010/09/24/alien-numbers/
5.
6. ;;; Run from the shell as so:
7. ;;; ~/programacao/programmingpraxis\$ ../clojure/clj-dev.sh alien-numbers.clj <<-END
8. ;;; > 2
9. ;;; > abc abcd 0123456789
10. ;;; > ([}} {([}? 0123456
11. ;;; > END
12. ;;;
13. ;;; Or execute its tests like so:
14. ;;; (clojure.test/run-tests 'euluis.aliennum)
15.
16. (ns euluis.aliennum
17.   (:use [clojure.test :only [is are testing deftest]]))
18.
19. ^L
20. ;;; Conversion given strings alien-num source-lang target-lang.
21. ;;; See convert-num fn which is the entry point.
22.
23. (defn decimal-to-lang
24.   "Converts decimal-num to the equivalent number in lang as a string.
25.  The algorithm was a translation of the algorithm by Rodrigo Menezes
26.  in C# that he posted in the programming praxis site."
27.   [decimal-num lang]
29.     (loop [decimal-value decimal-num lang-num []]
30.       (if (>= 0 decimal-value)
31.         (apply str lang-num)
32.         (recur (int (/ decimal-value lang-radix))
33.                (concat [(nth lang (mod decimal-value lang-radix))] lang-num))))))
34.
35. (deftest decimal-to-lang-test
36.   (testing "that given a decimal-num and a target lang, decimal-to-lang fn
37. converts it correctly to expected-num"
38.     (are [expected-num decimal-num lang]
39.          (= expected-num (decimal-to-lang decimal-num lang))
40.          "1100120" 987 "012"
41.          "33123" 987 "0123"
42.          "3" 3 "0123456789")))
43.
44. (defn digit-index
45.   [digit lang]
46.   (loop [i 0]
47.     (if (= digit (nth lang i))
48.       i
49.       (recur (inc i)))))
50.
51. (defn lang-to-decimal
52.   [alien-num lang]
54.         ralien-num (reverse alien-num)]
55.     (loop [i 0 decimal-num 0 product 1]
56.       (if (= i (count ralien-num))
57.         decimal-num
58.         (recur (inc i) (+ decimal-num (* (digit-index (nth ralien-num i) lang) product))
60.
61. (deftest lang-to-decimal-test
62.   (testing "that given an alien-num and a lang we can transform it to a decimal number"
63.     (are [expected-num alien-num lang]
64.          (= expected-num (lang-to-decimal alien-num lang))
65.          987 "1100120" "012"
66.          987 "33123" "0123"
67.          3 "11" "01")))
68.
69. (defn convert-num
70.   "Convert alien-num which is in source-lang into the same number in target-lang"
71.   [alien-num source-lang target-lang]
72.   (decimal-to-lang (lang-to-decimal alien-num source-lang) target-lang))
73.
74. (deftest convert-num-tests
75.   (testing "convert-num tests based on 2 to 36 radix numerals"
76.     (are [expected-num alien-num source-lang target-lang]
77.          (= expected-num
78.             (convert-num alien-num source-lang target-lang))
79.          ;; test: 987 0123456789 012 -> should be 1100120
80.          "1100120"
81.          "987" "0123456789" "012"
82.          "987"
83.          "1100120" "012" "0123456789"
84.          "33123"
85.          "987" "0123456789" "0123"
86.          "//-*/"
87.          "987" "0123456789" "+-*/"
88.          "RF"
89.          "987" "0123456789" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
90.          "35"
91.          "Z" "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"
92.          "cj"
93.          "\"!" "!\"#\$%&'()*+,-./:;?@[\\]^_`{|}~" "abcdefghij"
94.          "3"
95.          "11" "01" "0123456789")))
96.
97. ^L
98. ;;; Process the I/O
99. ;;; N
100. ;;; "<alien number>" "<sl0>..<slN>" "<tl0>..<tlM>"
101.
102. ;; production code to retrieve the lines containing the numbers to convert
105.   ([stream]
106.      (binding [*in* stream]
108.
109. (defn get-lines
110.   "Processes stream, returning a seq of lines in the order they are given
111.   and each line is guaranteed to be a string."
112.   [stream]
113.   (let [lines-number (Integer. (my-read-line stream))
114.         lines (loop [n lines-number lines '()]
115.                 (if (>= 0 n)
116.                   lines
117.                   (recur (dec n) (conj lines (my-read-line stream)))))]
118.     ;; what should be the behavior when we inconsistent number and lines?
119.     ;; I'll go for beneficent behavior...
120.     (reverse (filter #(not (nil? %)) lines))))
121.
122. ;; test code to retrieve the lines containing the numbers to convert
123. (defn stream-str
124.   "Returns an open BufferedReader from str."
125.   [str]
127.
128. (defn get-lines-str
129.   "Given str, creates and opens a stream from this and hands over to get-lines."
130.   [str]
131.   (with-open [s (stream-str str)]
132.     (get-lines s)))
133.
134. (deftest get-lines-tests
135.   (testing "get-lines for happy path test cases"
136.     (are [expected-lines in-str]
137.          (= expected-lines (get-lines-str in-str))
138.          ["123 abc" "4 5 7"] "2\n123 abc\n4 5 7\n"
139.          ;; No \\n in the end, but, get-lines has benevolent behavior.
140.          ["1" "2" "3"] "3\n1\n2\n3"
141.          ;; Although the specified number of lines is larger than the actual
142.          ;; lines, get-lines has benevolent behavior.
143.          ["1" "2" "3"] "4\n1\n2\n3"
144.          [] "0"
145.          [] "-1\n33\n44"))
146.   (testing "get-lines for exceptional cases"
147.     (is (thrown? NullPointerException (get-lines nil)))
148.     (are [str]
149.          (thrown? NumberFormatException (get-lines-str str))
150.          "ab1\n line 1"
151.          "1 not valid\n1")))
152.
153. ^L
154. ;; transform each line to convert, validating it and making it amiable for conversion
155.
156. ;; 0-9a-zA-Z!"#\$%&'()*+,-./:;?@[\]^_`{|}~
157. (def valid-chars (set (concat "!\"#\$%&'()*+,-./:;?@[\\]^_`{|}~"
158.                               (map char (range (int \0) (inc (int \9))))
159.                               (map char (range (int \a) (inc (int \z))))
160.                               (map char (range (int \A) (inc (int \Z)))))))
161.
162. (defn throw-illegal-arg
163.   "Throws IllegalArgumentException constructed with s."
164.   [s]
165.   (throw (IllegalArgumentException. s)))
166.
167. (defn if-line-invalid-throw
168.   "Checks if line-data is valid, throwing an IllegalArgumentException if it
169.   isn't."
170.   [line-data original-line]
171.   (if (not (= 3 (count line-data)))
172.     (throw-illegal-arg
173.      (str "Expected \"<alien-num> <source-lang> <target-lang>\", but, was: "
174.           original-line))
175.     (let [alien-num (first line-data)
176.           source-lang (second line-data)
177.           target-lang (nth line-data 2)]
178.       (cond
179.        (= (first alien-num) (first source-lang))
180.        (throw-illegal-arg
181.         "First digit of alien-num is the lowest valued digit of the source-lang.")
182.        (not (every? (set source-lang) alien-num))
183.        (throw-illegal-arg
184.         (str "Not all digits of alien-num (" alien-num
185.              ") are contained in the source-lang (" source-lang ")."))
186.        (not (= (count (set source-lang)) (count source-lang)))
187.        (throw-illegal-arg (str "Not all digits of the source-lang ("
188.                                source-lang ") are unique."))
189.        (not (= (count (set target-lang)) (count target-lang)))
190.        (throw-illegal-arg (str "Not all digits of the target-lang ("
191.                                target-lang ") are unique."))
192.        ;; 0-9a-zA-Z!"#\$%&'()*+,-./:;?@[\]^_`{|}~
193.        (not (every? valid-chars (set (concat alien-num source-lang target-lang))))
194.        (throw-illegal-arg (str "There are invalid chars in "
195.                                original-line "."))))))
196.
197. (defn process-line [s]
198.   "Being s an input line, transform and validate it."
199.   (let [split-chars #{\space \tab}
200.         line-data (remove #(some split-chars %)
201.                           (partition-by #(contains? split-chars %) s))]
202.     (if-line-invalid-throw line-data s)
203.     (map #(apply str %) line-data)))
204.
205. ;; tests for the transformation and validation functions
206. (deftest process-line-tests
207.   (testing "that given well formed lines the result is correct"
208.     (are [expected line]
209.          (= expected (process-line line))
210.          ["123" "0123456789" "abcdefghij"]
211.          "123 0123456789 abcdefghij"
212.          ;; notice that the benevolent behavior is guaranteed by accepting
213.          ;; a tab character and one space
214.          ["1" "01" "abc"]
215.          (str "1 01" \tab " abc")))
216.   (testing "that invalid input is rejected"
217.     (are [line]
218.          (thrown? IllegalArgumentException (process-line line))
219.          "1 0123 ABC xy" ; too many arguments
220.          "123 012345678" ; too few arguments
221.          ;; the number should not start with the lowest digit of the source language
222.          "012 01234 ABC"
223.          ;; the source language doesn't contain all digits in the number
224.          "12x 0123 xyz0"
225.          "12x x122 YZXO" ; the source language contains duplicate digits
226.          "12x x123 YZXY" ; the target language contains duplicate digits
227.          ;; the target language contains the invalid digit €
228.          "12x x123 €ZXY")))
229.
230. ^L
231. ;; output
232. (defn case-out
233.   [case-num converted-num]
234.   (str "Case #" case-num ": " converted-num))
235.
236. (deftest case-out-tests
237.   (is (= "Case #3: xyz!!"
238.          (case-out 3 "xyz!!"))
239.       "given a case-num and a converted-num a correct str is returned")
240.   (is (= "Case #55: java.lang.IllegalArgumentException: There are invalid chars in 12x x123 €ZXY."
241.          (case-out 55 (IllegalArgumentException. "There are invalid chars in 12x x123 €ZXY.")))))
242.
243. ^L
244. ;; the entry point
245. (defn main [args]
246.   (let [lines (get-lines *in*)
247.         numbered-lines (map-indexed (fn [case-num line] [(inc case-num) line]) lines)
248.         cases (map (fn [[case-num line]]
249.                      [case-num
250.                       (try (->> (process-line line)
251.                                 (apply convert-num))
252.                            (catch IllegalArgumentException e e))])
253.                    numbered-lines)]
254.     (doseq [[case-num result] cases]
255.       (println (case-out case-num result)))))
256.
257. (main *command-line-args*)
clone this paste RAW Paste Data
Top