#!/usr/bin/env hy ; =========== ; Excersise 2 ; =========== ; ; .. contents:: ; ; Prelude ; ======= ; ; Instead of `Python `_ this solution uses `Hy ; `_ to solve the assignments. Hy is a Lisp dialect ; implemented in Python which can use the Python ecosystem, including the standard ; library and third party modules. It has many functions, macros, and special ; forms to provide Python's language features in a ”lispy” syntax. ; ; Let us start with some imports of macros and functions used. The ``factorial`` ; function is used to check the results of our own implementations in `assignment ; 5 b`_. ; ; .. code-block:: hylang (require [hy.contrib.loop [loop]]) (import [math [factorial sqrt]]) ; Assignment 1 ; ============ ; ; **Q** Read an integer from the command line (hint: ``input`` function). Double ; the number and if it is positive. Halve the number if it is negative. Print the ; result. ; ; **A** Asking the user for integer numbers is part of several of the assignments, ; this subproblem is solved its own function which repeatedly asks the user until ; a suitable input was made. ; ; .. code-block:: hylang (defn ask-for-integer [] (while True (try (setv result (int (input "Enter integer: "))) (except [ValueError] (print "Not a number, please try again.")) (else (break)))) result) ; .. admonition:: The DRY Principle ; ; Functions are one way to follow the DRY principle: „**D**\on't **R**\epeat ; **Y**\ourself“. Programmers strive to avoid redundancy in code and data. ; ; Testrun:: ; ; => (ask-for-integer) ; Enter integer: ; Not a number, please try again. ; Enter integer: x ; Not a number, please try again. ; Enter integer: 42 ; 42 ; ; The solution asks the user for a number with the ``ask-for-integer`` function ; just defined above and then prints the result of either dividing or multiplying ; the value by two. ; ; .. code-block:: hylang (defn assignment-1 [] (let [value (ask-for-integer)] (print ((if (neg? value) / *) value 2)))) ; Testrun:: ; ; => (assignment-1) ; Enter integer: 5 ; 10 ; => (assignment-1) ; Enter integer: -5 ; -2.5 ; ; Assignment 2 ; ============ ; ; **Q** Define a list of at least ten strings. Print every second string. ; ; **A** This seems straight forward. The function declares a list with the first ; ten letters of the alphabet, "a" to "j", and then uses ``take-nth`` to get every ; second element to ``print`` it. ; ; .. code-block:: hylang (defn assignment-2 [] (let [strings ["a" "b" "c" "d" "e" "f" "g" "h" "i" "j"]] (for [s (take-nth 2 strings)] (print s)))) ; Testrun:: ; ; => (assignment-2) ; a ; c ; e ; g ; i ; ; Assignment 3 ; ============ ; ; **Q** Read numbers from the command line. Add them up until 0 is entered. ; Afterwards print the sum. ; ; **A** As reading a sequence of numbers from the command line, with zero as end ; marker, is part of both this and `assignment 4`_ we write a short function for ; this. DRY again. ; ; First we inform the user what we expect and how the input can be ended. Then ; the function uses ``iter`` to create an iterable which repeatedly calls ; ``ask-for-integer`` defined in `assignment 1`_ until that function returns a ; zero. ; ; .. code-block:: hylang (defn ask-for-integers [] (print "Enter integers. Enter 0 to end.") (iter ask-for-integer 0)) ; Testrun:: ; ; => (list (ask-for-integers)) ; Enter integers. Enter 0 to end. ; Enter integer: 1 ; Enter integer: -2 ; Enter integer: x ; Not a number, please try again. ; Enter integer: 3 ; Enter integer: 0 ; [1, -2, 3] ; ; Now the actual function becomes trivial: Just sum the elements of the iterable ; returned by ``ask-for-integers``. ; ; .. code-block:: hylang (defn assignment-3 [] (print (sum (ask-for-integers)))) ; Testrun:: ; ; => (assignment-3) ; Enter integers. Enter 0 to end. ; Enter integer: 1 ; Enter integer: 2 ; Enter integer: 3 ; Enter integer: -4 ; Enter integer: 0 ; 2 ; ; Assignment 4 ; ============ ; ; **Q** Read numbers from the command line. Sum them up if they are positive, ; ignore them if they are negative and stop when 0 is entered. Use ``continue`` ; and ``break``! ; ; **A** The function starts by setting the local variable ``result`` to 0. ; Then it goes with a ``for`` loop over the numbers entered by the user based ; by repeatedly calling ``ask-for-integer`` defined in `assignment 1`_. ; ; For each ``value`` it then checks two conditions from the question and ; acts accordingly. If the value is 0 the ``for`` loop is left by ``break``. ; If the value is negative the loop ``continue``\s with the next value from ; the user. That leaves only positive values so the result is updated by adding ; the value to the current result at the end, if the loop was not left by ; ``break`` and the last expression was not skipped by ``continue``. ; ; .. code-block:: hylang (defn assignment-4 [] (setv result 0) (for [value (repeatedly ask-for-integer)] (if (zero? value) (break)) (if (neg? value) (continue)) (setv result (+ result value))) (print result)) ; Testrun:: ; ; => (assignment-4) ; Enter integer: 1 ; Enter integer: -2 ; Enter integer: 3 ; Enter integer: -4 ; Enter integer: 0 ; 4 ; ; The ``if``\s are ordered in a way there are no unneccessary checks made. ; First we check for zero because then the other two checks are irrelevant. ; Then the negativ check is made because if it is true we can skip the positive ; check. ; ; But wait, didn't we define ``ask-for-integers`` in `assignment 3`_ for ; repeatedly asking the user for numbers? Yes we did. But if we used it here we ; would have a hard time to justify the zero test and the ``break`` because ; ``ask-for-integers`` already stops at zero, so the first condition is never met ; and we have no use for ``break``. ; ; ``continue`` is also only neccessary because of the way the tests are odered and ; expressed. Instead of skipping the addition at the end of the ``for`` loop, we ; could reverse the test and only execute the addition if the result is positive. ; All this leads to a much simpler and shorter function: ; ; .. code-block:: hylang (defn assignment-4-simplified [] (setv result 0) (for [value (ask-for-integers)] (if (pos? value) (setv result (+ result value)))) (print result)) ; Testrun:: ; ; => (assignment-4-simplified) ; Enter integers. Enter 0 to end. ; Enter integer: 1 ; Enter integer: -2 ; Enter integer: 3 ; Enter integer: -4 ; Enter integer: 0 ; 4 ; ; A function which still is not the most elegant and concise way to solve this ; problem. We want to ``filter`` the numbers so only positive ones are summed. ; Extending the solution from `assignment 3`_ we get: ; ; .. code-block:: hylang (defn assignment-4-concise [] (print (sum (filter pos? (ask-for-integers))))) ; Testrun:: ; ; => (assignment-4-concise) ; Enter integers. Enter 0 to end. ; Enter integer: 1 ; Enter integer: -2 ; Enter integer: 3 ; Enter integer: -4 ; Enter integer: 0 ; 4 ; ; Assignment 5 ; ============ ; ; **Q** Try do solve at least one of `assignment 5 b`_ and `assignment 5 c`_, if ; you can (yes, they are a bit tougher!) ; ; Assignment 5 a ; -------------- ; ; **Q** Write a function which returns the absolute difference between two numbers ; `a` and `b`: \| `a`-`b` \|. Call the function for five combinations for `a` and ; `b`. ; ; **A** Given there is an ``abs`` function and the substraction is a very basic ; operation this almost seems to be too simple. ; ; .. code-block:: hylang (defn assignment-5-a [a b] (abs (- a b))) ; And a small function to test five pairs of numbers against their expected ; results. ; ; .. code-block:: hylang (defn test-assignment-5-a [] (for [[a b expected] [[1 2 1] [2 1 1] [2 2 0] [-1 1 2] [1 -1 2]]] (assert (= (assignment-5-a a b) expected)))) ; Testrun:: ; ; => (test-assignment-5-a) ; ; Assignment 5 b ; -------------- ; ; **Q** Write a function that computes and returns the factorial of any positive ; integer. Use either a ``for`` loop, a ``while`` loop, or recursion. Call the ; function for 13 and print the returned factorial to the console. ; ; **A** We don't just solve `assignment 5 b`_ *and* `5 c `_ but ; also all three alternatives for looping: ``for``, ``while``, and recursion. Plus ; an implementation with no explicit code for looping at all, leveraging the power ; of the rich set of functional building blocks available in Python and therefore ; also in Hy. ; ; As factorial is defined for positive numbers only we start with a small function ; to check if a number is negative. It throws an ``ValueError`` exception if the ; number is negative and returns the number otherwise. The check is in an own ; function to avoid repeating the code in each of the following implementations. ; ; .. code-block:: hylang (defn check-positive [n] (if (neg? n) (raise (ValueError "value must be positive")) n)) ; Testrun:: ; ; => (check-positive 42) ; 42 ; => (check-positive -23) ; Traceback (most recent call last): ; ... ; ValueError: value must be positive ; => (check-positive 0) ; 0 ; ; ``factorial`` with ``for`` ; ^^^^^^^^^^^^^^^^^^^^^^^^^^ ; ; After initialising the result to 1 the ``for`` loop iterates over the range of ; integer numbers from 1 to the given number. The upper bound is checked if it is ; positive and the increased by 1 because ``range`` yields the range of numbers ; *excluding* the given endpoint. Within the loop all numbers are multiplied to ; the result. ; ; .. code-block:: hylang (defn assignment-5-b-1 [n] (setv result 1) (for [x (range 1 (inc (check-positive n)))] (setv result (* result x))) result) ; ``factorial`` with ``while`` ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; ; First the function checks if the given number is positive. Then the result is ; initialised with 1. As long as the number is greater that one, the result is ; multiplied with the current number, which is decreased by one before the next ; iteration of the loop starts. ; ; This is the most ”primitive”, imperative variant of the four alternatives ; presented here, as we had to spell out each step explicitly, in the exact order ; it is executed by the computer. Although the `recursive factorial`_ in the very ; next section has the same amount of expressions, the order of execution of each ; single one is not that obviously encoded, as initialising and updating the two ; variables looks more ”parallel” there. ; ; .. code-block:: hylang (defn assignment-5-b-2 [n] (check-positive n) (setv result 1) (while (> n 1) (setv result (* result n) n (dec n))) result) ; Recursive ``factorial`` ; ^^^^^^^^^^^^^^^^^^^^^^^ ; ; For recursion we use the ``loop`` and ``recur`` macros. ``loop`` is used to ; define and call an anonymous function and ``recur`` calls this function ; recursively. The advantage of ``loop`` and ``recur`` over defining an ordinary ; function is that those macros do a little extra work to do `tail call ; optimization`_ (TCO) for which the function must be tail recursive. ; ; .. _tail call optimization: ; https://stackoverflow.com/questions/310974/what-is-tail-call-optimization#310980 ; ; The recursive function gets the number to calculatue the factorial of and the ; result which is given as 1 at the first call. If the number is smaller than 1 ; the result is returned, else the function is called recursively with the number ; decreased by one and the accumulated result mulipliplied by the (non-decreased) ; number. ; ; .. code-block:: hylang (defn assignment-5-b-3 [n] (loop [[i (check-positive n)] [result 1]] (if (< i 1) result (recur (dec i) (* result i))))) ; Without ``loop`` and ``recur`` it would look like this and there would be no ; TCO: ; ; .. code-block:: hylang (defn assignment-5-b-3-without-loop [n] (let [f (fn [i result] (if (< i 1) result (f (dec i) (* result i))))] (f (check-positive n) 1))) ; ``factorial`` with functional building blocks ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; ; Practising different kind of looping techniques and learning when to use them is ; important. So is knowing the functional building blocks to write concise and ; elegant solutions without explicit loops. Here we can use ``reduce``. It takes ; a binary function, an iterable of values, and an initial value, and applies the ; given function to each of the values from the iterable, using the call from the ; last result as first argument — or the given initial argument for the very first ; call. ; ; .. code-block:: hylang (defn assignment-5-b-4 [n] (reduce * (range 1 (inc (check-positive n))) 1)) ; Testing ``factorial`` ; ^^^^^^^^^^^^^^^^^^^^^ ; ; For testing all the factorial implementations we first calculate the expected ; result by means of the ``factorial`` function from the ``math`` module. Then ; all four variations defined above will be called an their result is ; ``assert``\ed to be the expected. ; ; .. code-block:: hylang (defn test-assignment-5-b [] (let [n 13 expected (factorial n)] (for [f [assignment-5-b-1 assignment-5-b-2 assignment-5-b-3 assignment-5-b-4]] (let [result (f n)] (assert (= result expected)) (print result))))) ; Testrun:: ; ; => (test-assignment-5-b) ; 6227020800 ; 6227020800 ; 6227020800 ; 6227020800 ; ; Assignment 5 c ; -------------- ; ; **Q** Write a function that decomposes any positive integer into its prime ; factors and returns them as a list. Call the function for 12341234 and print the ; list of prime factors to the console. ; ; **A** The basic idea for a solution is to to test numbers from 2 on upwards as ; candidates for (prime) factors and each time a candidate divides the number ; without a remainder to take the quotient as new number and note down the ; candidate as a prime factor. Although it seems tempting to determine prime ; numbers upfront to test them as factors, that step would not be necessary and ; will not speed up the solution unless we would cache those prime numbers for ; multiple calls to the function. ; ; The implementation uses two simple optimizations: ; ; * We do not test every number from 2 on upwards but just 2 and then every *odd* ; number greater than 2; ; ; * and we do not test the numbers up to the current number we want to factor but ; just until the candidate reaches the square root of that number, as between ; that and the number there can not be another factor. ; ; First we create an iterable with the candidates by chaining the list containing ; the 2 and an iterable counting from 3 on in steps of 2. In other words odd ; numbers. ; ; Then we start a loop function with the number, its square root as upper limit ; for the candidates, and an empty list for the result as arguments. ; ; After getting the next candidate it is checked against the current limit value. ; If the limit is lower than the candidate we are done and know the current number ; is itself the last prime factor needing to be ``cons``\ed to the result and ; being the result of the function. ; ; Otherwise the quotient and the remainder for the number and the candidate are ; calculated. If the remainder is zero the loop function is called recursively ; with the quotient as the new number to factor, its square root as new limit and ; a result that is extended by the candidate. For a non-zero remainder the loop ; function is called with the same arguments from the previous call to check the ; next candidate. ; ; .. code-block:: hylang (defn assignment-5-c [n] (let [candidates (chain [2] (count 3 2))] (loop [[number n] [limit (sqrt n)] [result []]] (let [candidate (next candidates)] (if (< limit candidate) (cons number result) (let [[quotient remainder] (divmod number candidate)] (if (zero? remainder) (recur quotient (sqrt quotient) (cons candidate result)) (recur number limit result)))))))) ; The result to test against was calculated with the linux command line tool ; ``factor``. As ``cons`` prepends elements to the list, the comparison value ; goes from the highest to the lowest prime factor. ; ; .. code-block:: hylang (defn test-assignment-5-c [] (let [result (assignment-5-c 12341234)] (assert (= result [617 137 73 2])) (print result))) ; Testrun:: ; ; => (test-assignment-5-c) ; [617, 137, 73, 2] ; ; .. pylit.py --comment-string='; ' -m '.. code-block:: hylang' -c test.hy ; .. pylit.py --comment-string='; ' -m '.. code-block:: hylang' -t test.hy.txt ; .. rst2pdf -c --footer '###Page###' -e dotted_toc test.hy.txt