Advertisement
Marrin

Excercise II

Nov 6th, 2016
156
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Clojure 17.08 KB | None | 0 0
  1. #!/usr/bin/env hy
  2.  
  3. ; ===========
  4. ; Excersise 2
  5. ; ===========
  6. ;
  7. ; .. contents::
  8. ;
  9. ; Prelude
  10. ; =======
  11. ;
  12. ; Instead of `Python <http://www.python.org/>`_ this solution uses `Hy
  13. ; <http://www.hylang.org/>`_ to solve the assignments.  Hy is a Lisp dialect
  14. ; implemented in Python which can use the Python ecosystem, including the standard
  15. ; library and third party modules.  It has many functions, macros, and special
  16. ; forms to provide Python's language features in a ”lispy” syntax.
  17. ;
  18. ; Let us start with some imports of macros and functions used.  The ``factorial``
  19. ; function is used to check the results of our own implementations in `assignment
  20. ; 5 b`_.
  21. ;
  22. ; .. code-block:: hylang
  23.  
  24. (require [hy.contrib.loop [loop]])
  25. (import [math [factorial sqrt]])
  26.  
  27. ; Assignment 1
  28. ; ============
  29. ;
  30. ; **Q** Read an integer from the command line (hint: ``input`` function). Double
  31. ; the number and if it is positive. Halve the number if it is negative. Print the
  32. ; result.
  33. ;
  34. ; **A** Asking the user for integer numbers is part of several of the assignments,
  35. ; this subproblem is solved its own function which repeatedly asks the user until
  36. ; a suitable input was made.
  37. ;
  38. ; .. code-block:: hylang
  39.  
  40. (defn ask-for-integer []
  41.   (while True
  42.     (try (setv result (int (input "Enter integer: ")))
  43.       (except [ValueError] (print "Not a number, please try again."))
  44.       (else (break))))
  45.   result)
  46.  
  47. ; .. admonition:: The DRY Principle
  48. ;
  49. ;   Functions are one way to follow the DRY principle: „**D**\on't **R**\epeat
  50. ;   **Y**\ourself“.  Programmers strive to avoid redundancy in code and data.
  51. ;
  52. ; Testrun::
  53. ;
  54. ;   => (ask-for-integer)
  55. ;   Enter integer:
  56. ;   Not a number, please try again.
  57. ;   Enter integer: x
  58. ;   Not a number, please try again.
  59. ;   Enter integer: 42
  60. ;   42
  61. ;
  62. ; The solution asks the user for a number with the ``ask-for-integer`` function
  63. ; just defined above and then prints the result of either dividing or multiplying
  64. ; the value by two.
  65. ;
  66. ; .. code-block:: hylang
  67.  
  68. (defn assignment-1 []
  69.   (let [value (ask-for-integer)]
  70.     (print ((if (neg? value) / *) value 2))))
  71.  
  72. ; Testrun::
  73. ;
  74. ;   => (assignment-1)
  75. ;   Enter integer: 5
  76. ;   10
  77. ;   => (assignment-1)
  78. ;   Enter integer: -5
  79. ;   -2.5
  80. ;
  81. ; Assignment 2
  82. ; ============
  83. ;
  84. ; **Q** Define a list of at least ten strings. Print every second string.
  85. ;
  86. ; **A** This seems straight forward. The function declares a list with the first
  87. ; ten letters of the alphabet, "a" to "j", and then uses ``take-nth`` to get every
  88. ; second element to ``print`` it.
  89. ;
  90. ; .. code-block:: hylang
  91.  
  92. (defn assignment-2 []
  93.   (let [strings ["a" "b" "c" "d" "e" "f" "g" "h" "i" "j"]]
  94.     (for [s (take-nth 2 strings)] (print s))))
  95.  
  96. ; Testrun::
  97. ;
  98. ;   => (assignment-2)
  99. ;   a
  100. ;   c
  101. ;   e
  102. ;   g
  103. ;   i
  104. ;
  105. ; Assignment 3
  106. ; ============
  107. ;
  108. ; **Q** Read numbers from the command line. Add them up until 0 is entered.
  109. ; Afterwards print the sum.
  110. ;
  111. ; **A** As reading a sequence of numbers from the command line, with zero as end
  112. ; marker, is part of both this and `assignment 4`_ we write a short function for
  113. ; this. DRY again.
  114. ;
  115. ; First we inform the user what we expect and how the input can be ended.  Then
  116. ; the function uses ``iter`` to create an iterable which repeatedly calls
  117. ; ``ask-for-integer`` defined in `assignment 1`_ until that function returns a
  118. ; zero.
  119. ;
  120. ; .. code-block:: hylang
  121.  
  122. (defn ask-for-integers []
  123.   (print "Enter integers. Enter 0 to end.")
  124.   (iter ask-for-integer 0))
  125.  
  126. ; Testrun::
  127. ;
  128. ;   => (list (ask-for-integers))
  129. ;   Enter integers. Enter 0 to end.
  130. ;   Enter integer: 1
  131. ;   Enter integer: -2
  132. ;   Enter integer: x
  133. ;   Not a number, please try again.
  134. ;   Enter integer: 3
  135. ;   Enter integer: 0
  136. ;   [1, -2, 3]
  137. ;
  138. ; Now the actual function becomes trivial: Just sum the elements of the iterable
  139. ; returned by ``ask-for-integers``.
  140. ;
  141. ; .. code-block:: hylang
  142.  
  143. (defn assignment-3 []
  144.   (print (sum (ask-for-integers))))
  145.  
  146. ; Testrun::
  147. ;
  148. ;   => (assignment-3)
  149. ;   Enter integers. Enter 0 to end.
  150. ;   Enter integer: 1
  151. ;   Enter integer: 2
  152. ;   Enter integer: 3
  153. ;   Enter integer: -4
  154. ;   Enter integer: 0
  155. ;   2
  156. ;
  157. ; Assignment 4
  158. ; ============
  159. ;
  160. ; **Q** Read numbers from the command line. Sum them up if they are positive,
  161. ; ignore them if they are negative and stop when 0 is entered. Use ``continue``
  162. ; and ``break``!
  163. ;
  164. ; **A** The function starts by setting the local variable ``result`` to 0.
  165. ; Then it goes with a ``for`` loop over the numbers entered by the user based
  166. ; by repeatedly calling ``ask-for-integer`` defined in `assignment 1`_.
  167. ;
  168. ; For each ``value`` it then checks two conditions from the question and
  169. ; acts accordingly.  If the value is 0 the ``for`` loop is left by ``break``.
  170. ; If the value is negative the loop ``continue``\s with the next value from
  171. ; the user.  That leaves only positive values so the result is updated by adding
  172. ; the value to the current result at the end, if the loop was not left by
  173. ; ``break`` and the last expression was not skipped by ``continue``.
  174. ;
  175. ; .. code-block:: hylang
  176.  
  177. (defn assignment-4 []
  178.   (setv result 0)
  179.   (for [value (repeatedly ask-for-integer)]
  180.     (if (zero? value) (break))
  181.     (if (neg? value) (continue))
  182.     (setv result (+ result value)))
  183.   (print result))
  184.  
  185. ; Testrun::
  186. ;
  187. ;   => (assignment-4)
  188. ;   Enter integer: 1
  189. ;   Enter integer: -2
  190. ;   Enter integer: 3
  191. ;   Enter integer: -4
  192. ;   Enter integer: 0
  193. ;   4
  194. ;
  195. ; The ``if``\s are ordered in a way there are no unneccessary checks made.
  196. ; First we check for zero because then the other two checks are irrelevant.
  197. ; Then the negativ check is made because if it is true we can skip the positive
  198. ; check.
  199. ;
  200. ; But wait, didn't we define ``ask-for-integers`` in `assignment 3`_ for
  201. ; repeatedly asking the user for numbers?  Yes we did.  But if we used it here we
  202. ; would have a hard time to justify the zero test and the ``break`` because
  203. ; ``ask-for-integers`` already stops at zero, so the first condition is never met
  204. ; and we have no use for ``break``.
  205. ;
  206. ; ``continue`` is also only neccessary because of the way the tests are odered and
  207. ; expressed.  Instead of skipping the addition at the end of the ``for`` loop, we
  208. ; could reverse the test and only execute the addition if the result is positive.
  209. ; All this leads to a much simpler and shorter function:
  210. ;
  211. ; .. code-block:: hylang
  212.  
  213. (defn assignment-4-simplified []
  214.   (setv result 0)
  215.   (for [value (ask-for-integers)]
  216.     (if (pos? value) (setv result (+ result value))))
  217.   (print result))
  218.  
  219. ; Testrun::
  220. ;
  221. ;   => (assignment-4-simplified)
  222. ;   Enter integers. Enter 0 to end.
  223. ;   Enter integer: 1
  224. ;   Enter integer: -2
  225. ;   Enter integer: 3
  226. ;   Enter integer: -4
  227. ;   Enter integer: 0
  228. ;   4
  229. ;
  230. ; A function which still is not the most elegant and concise way to solve this
  231. ; problem.  We want to ``filter`` the numbers so only positive ones are summed.
  232. ; Extending the solution from `assignment 3`_ we get:
  233. ;
  234. ; .. code-block:: hylang
  235.  
  236. (defn assignment-4-concise []
  237.   (print (sum (filter pos? (ask-for-integers)))))
  238.  
  239. ; Testrun::
  240. ;
  241. ;   => (assignment-4-concise)
  242. ;   Enter integers. Enter 0 to end.
  243. ;   Enter integer: 1
  244. ;   Enter integer: -2
  245. ;   Enter integer: 3
  246. ;   Enter integer: -4
  247. ;   Enter integer: 0
  248. ;   4
  249. ;
  250. ; Assignment 5
  251. ; ============
  252. ;
  253. ; **Q** Try do solve at least one of `assignment 5 b`_ and `assignment 5 c`_, if
  254. ; you can (yes, they are a bit tougher!)
  255. ;
  256. ; Assignment 5 a
  257. ; --------------
  258. ;
  259. ; **Q** Write a function which returns the absolute difference between two numbers
  260. ; `a` and `b`: \| `a`-`b` \|. Call the function for five combinations for `a` and
  261. ; `b`.
  262. ;
  263. ; **A** Given there is an ``abs`` function and the substraction is a very basic
  264. ; operation this almost seems to be too simple.
  265. ;
  266. ; .. code-block:: hylang
  267.  
  268. (defn assignment-5-a [a b]
  269.   (abs (- a b)))
  270.  
  271. ; And a small function to test five pairs of numbers against their expected
  272. ; results.
  273. ;
  274. ; .. code-block:: hylang
  275.  
  276. (defn test-assignment-5-a []
  277.   (for [[a b expected] [[1 2 1] [2 1 1] [2 2 0] [-1 1 2] [1 -1 2]]]
  278.     (assert (= (assignment-5-a a b) expected))))
  279.  
  280. ; Testrun::
  281. ;
  282. ;   => (test-assignment-5-a)
  283. ;
  284. ; Assignment 5 b
  285. ; --------------
  286. ;
  287. ; **Q** Write a function that computes and returns the factorial of any positive
  288. ; integer. Use either a ``for`` loop, a ``while`` loop, or recursion. Call the
  289. ; function for 13 and print the returned factorial to the console.
  290. ;
  291. ; **A** We don't just solve `assignment 5 b`_ *and* `5 c <assignment 5 c>`_ but
  292. ; also all three alternatives for looping: ``for``, ``while``, and recursion. Plus
  293. ; an implementation with no explicit code for looping at all, leveraging the power
  294. ; of the rich set of functional building blocks available in Python and therefore
  295. ; also in Hy.
  296. ;
  297. ; As factorial is defined for positive numbers only we start with a small function
  298. ; to check if a number is negative.  It throws an ``ValueError`` exception if the
  299. ; number is negative and returns the number otherwise.  The check is in an own
  300. ; function to avoid repeating the code in each of the following implementations.
  301. ;
  302. ; .. code-block:: hylang
  303.  
  304. (defn check-positive [n]
  305.   (if (neg? n) (raise (ValueError "value must be positive")) n))
  306.  
  307. ; Testrun::
  308. ;
  309. ;   => (check-positive 42)
  310. ;   42
  311. ;   => (check-positive -23)
  312. ;   Traceback (most recent call last):
  313. ;     ...
  314. ;   ValueError: value must be positive
  315. ;   => (check-positive 0)
  316. ;   0
  317. ;
  318. ; ``factorial`` with ``for``
  319. ; ^^^^^^^^^^^^^^^^^^^^^^^^^^
  320. ;
  321. ; After initialising the result to 1 the ``for`` loop iterates over the range of
  322. ; integer numbers from 1 to the given number.  The upper bound is checked if it is
  323. ; positive and the increased by 1 because ``range`` yields the range of numbers
  324. ; *excluding* the given endpoint.  Within the loop all numbers are multiplied to
  325. ; the result.
  326. ;
  327. ; .. code-block:: hylang
  328.  
  329. (defn assignment-5-b-1 [n]
  330.   (setv result 1)
  331.   (for [x (range 1 (inc (check-positive n)))]
  332.     (setv result (* result x)))
  333.   result)
  334.  
  335. ; ``factorial`` with ``while``
  336. ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  337. ;
  338. ; First the function checks if the given number is positive.  Then the result is
  339. ; initialised with 1.  As long as the number is greater that one, the result is
  340. ; multiplied with the current number, which is decreased by one before the next
  341. ; iteration of the loop starts.
  342. ;
  343. ; This is the most ”primitive”, imperative variant of the four alternatives
  344. ; presented here, as we had to spell out each step explicitly, in the exact order
  345. ; it is executed by the computer.  Although the `recursive factorial`_ in the very
  346. ; next section has the same amount of expressions, the order of execution of each
  347. ; single one is not that obviously encoded, as initialising and updating the two
  348. ; variables looks more ”parallel” there.
  349. ;
  350. ; .. code-block:: hylang
  351.  
  352. (defn assignment-5-b-2 [n]
  353.   (check-positive n)
  354.   (setv result 1)
  355.   (while (> n 1)
  356.     (setv result (* result n)
  357.           n (dec n)))
  358.   result)
  359.  
  360. ; Recursive ``factorial``
  361. ; ^^^^^^^^^^^^^^^^^^^^^^^
  362. ;
  363. ; For recursion we use the ``loop`` and ``recur`` macros.  ``loop`` is used to
  364. ; define and call an anonymous function and ``recur`` calls this function
  365. ; recursively. The advantage of ``loop`` and ``recur`` over defining an ordinary
  366. ; function is that those macros do a little extra work to do `tail call
  367. ; optimization`_ (TCO) for which the function must be tail recursive.
  368. ;
  369. ; .. _tail call optimization:
  370. ;   https://stackoverflow.com/questions/310974/what-is-tail-call-optimization#310980
  371. ;
  372. ; The recursive function gets the number to calculatue the factorial of and the
  373. ; result which is given as 1 at the first call.  If the number is smaller than 1
  374. ; the result is returned, else the function is called recursively with the number
  375. ; decreased by one and the accumulated result mulipliplied by the (non-decreased)
  376. ; number.
  377. ;
  378. ; .. code-block:: hylang
  379.  
  380. (defn assignment-5-b-3 [n]
  381.   (loop [[i (check-positive n)] [result 1]]
  382.     (if (< i 1)
  383.       result
  384.       (recur (dec i) (* result i)))))
  385.  
  386. ; Without ``loop`` and ``recur`` it would look like this and there would be no
  387. ; TCO:
  388. ;
  389. ; .. code-block:: hylang
  390.  
  391. (defn assignment-5-b-3-without-loop [n]
  392.   (let [f (fn [i result]
  393.               (if (< i 1)
  394.                 result
  395.                 (f (dec i) (* result i))))]
  396.     (f (check-positive n) 1)))
  397.  
  398. ; ``factorial`` with functional building blocks
  399. ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  400. ;
  401. ; Practising different kind of looping techniques and learning when to use them is
  402. ; important.  So is knowing the functional building blocks to write concise and
  403. ; elegant solutions without explicit loops.  Here we can use ``reduce``.  It takes
  404. ; a binary function, an iterable of values, and an initial value, and applies the
  405. ; given function to each of the values from the iterable, using the call from the
  406. ; last result as first argument — or the given initial argument for the very first
  407. ; call.
  408. ;
  409. ; .. code-block:: hylang
  410.  
  411. (defn assignment-5-b-4 [n]
  412.   (reduce * (range 1 (inc (check-positive n))) 1))
  413.  
  414. ; Testing ``factorial``
  415. ; ^^^^^^^^^^^^^^^^^^^^^
  416. ;
  417. ; For testing all the factorial implementations we first calculate the expected
  418. ; result by means of the ``factorial`` function from the ``math`` module.  Then
  419. ; all four variations defined above will be called an their result is
  420. ; ``assert``\ed to be the expected.
  421. ;
  422. ; .. code-block:: hylang
  423.  
  424. (defn test-assignment-5-b []
  425.   (let [n 13
  426.         expected (factorial n)]
  427.     (for [f [assignment-5-b-1
  428.              assignment-5-b-2
  429.              assignment-5-b-3
  430.              assignment-5-b-4]]
  431.       (let [result (f n)]
  432.         (assert (= result expected))
  433.         (print result)))))
  434.  
  435. ; Testrun::
  436. ;
  437. ;   => (test-assignment-5-b)
  438. ;   6227020800
  439. ;   6227020800
  440. ;   6227020800
  441. ;   6227020800
  442. ;
  443. ; Assignment 5 c
  444. ; --------------
  445. ;
  446. ; **Q** Write a function that decomposes any positive integer into its prime
  447. ; factors and returns them as a list. Call the function for 12341234 and print the
  448. ; list of prime factors to the console.
  449. ;
  450. ; **A** The basic idea for a solution is to to test numbers from 2 on upwards as
  451. ; candidates for (prime) factors and each time a candidate divides the number
  452. ; without a remainder to take the quotient as new number and note down the
  453. ; candidate as a prime factor.  Although it seems tempting to determine prime
  454. ; numbers upfront to test them as factors, that step would not be necessary and
  455. ; will not speed up the solution unless we would cache those prime numbers for
  456. ; multiple calls to the function.
  457. ;
  458. ; The implementation uses two simple optimizations:
  459. ;
  460. ; * We do not test every number from 2 on upwards but just 2 and then every *odd*
  461. ;   number greater than 2;
  462. ;
  463. ; * and we do not test the numbers up to the current number we want to factor but
  464. ;   just until the candidate reaches the square root of that number, as between
  465. ;   that and the number there can not be another factor.
  466. ;
  467. ; First we create an iterable with the candidates by chaining the list containing
  468. ; the 2 and an iterable counting from 3 on in steps of 2.  In other words odd
  469. ; numbers.
  470. ;
  471. ; Then we start a loop function with the number, its square root as upper limit
  472. ; for the candidates, and an empty list for the result as arguments.
  473. ;
  474. ; After getting the next candidate it is checked against the current limit value.
  475. ; If the limit is lower than the candidate we are done and know the current number
  476. ; is itself the last prime factor needing to be ``cons``\ed to the result and
  477. ; being the result of the function.
  478. ;
  479. ; Otherwise the quotient and the remainder for the number and the candidate are
  480. ; calculated.  If the remainder is zero the loop function is called recursively
  481. ; with the quotient as the new number to factor, its square root as new limit and
  482. ; a result that is extended by the candidate.  For a non-zero remainder the loop
  483. ; function is called with the same arguments from the previous call to check the
  484. ; next candidate.
  485. ;
  486. ; .. code-block:: hylang
  487.  
  488. (defn assignment-5-c [n]
  489.   (let [candidates (chain [2] (count 3 2))]
  490.     (loop [[number n] [limit (sqrt n)] [result []]]
  491.       (let [candidate (next candidates)]
  492.         (if (< limit candidate)
  493.           (cons number result)
  494.           (let [[quotient remainder] (divmod number candidate)]
  495.             (if (zero? remainder)
  496.               (recur quotient (sqrt quotient) (cons candidate result))
  497.               (recur number limit result))))))))
  498.  
  499. ; The result to test against was calculated with the linux command line tool
  500. ; ``factor``.  As ``cons`` prepends elements to the list, the comparison value
  501. ; goes from the highest to the lowest prime factor.
  502. ;
  503. ; .. code-block:: hylang
  504.  
  505. (defn test-assignment-5-c []
  506.   (let [result (assignment-5-c 12341234)]
  507.     (assert (= result [617 137 73 2]))
  508.     (print result)))
  509.  
  510. ; Testrun::
  511. ;
  512. ;   => (test-assignment-5-c)
  513. ;   [617, 137, 73, 2]
  514. ;
  515. ; .. pylit.py --comment-string='; ' -m '.. code-block:: hylang' -c test.hy
  516. ; .. pylit.py --comment-string='; ' -m '.. code-block:: hylang' -t test.hy.txt
  517. ; .. rst2pdf -c --footer '###Page###' -e dotted_toc test.hy.txt
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement