Welcome to MCS 275 ================== The goal of this second introductory programming course is to explore the capabilities offered Python and to practice some useful problem solving techniques such as backtracking. Version 3.6 of Python will be used. On occasion, we mention some important differences with version 2. In this lecture we cover the writing of an algorithm from an input/output statement; followed by the coding of the algorithm in a Python script. Programming in Python --------------------- We distinguish between *programming in the small* and *programming in the large*. As a scripting language, Python offers many advantages for small programs: 1. As an interpreted language, the immediate execution lends itself well to rapid prototyping. Python offers dynamic typing and automatic garbage collection which frees the user from the perils of memory allocation and deallocation. 2. Although not as efficient as compiled code, Python is open to extension modules for C code. Computationally intensive tasks can therefore remain in compiled code and invoked from Python. For writing large programs, Python offers two benefits: 1. Python is an object oriented language. Object Oriented Programming (OOP) aims to organize the code to promote reuse, change, and scalability. 2. Python comes with *batteries included*. The Open Source revolution has given us LAMP, where * L stands for Linux, the operating system, * A is for Apache, the web server, * M is for MySQL, the database, and * P is for Python, the scripting language. Python glues many important software components. Factorization in Primes ----------------------- As an example problem to introduce programming, weconsider the factorization in primes. A prime has only two divisors: 1 and itself. Every natural number can be factored as a unique product of primes. The input/output statement of our problem is listed below: .. math:: \begin{array}{rcl} input & : & \mbox{a natural number } n \\ output & : & \mbox{a list of primes } [p_1,p_2,\ldots,p_k], \\ & & n = p_1 \times p_2 \times \cdots \times p_k \end{array} After we understand the problem statement, a first step in solving the problem is to think about simplifications. Are there related, easier problems we can solve? In this case, there is. We can list all divisors of a number. The input/output statement of this simpler problem is listed below: .. math:: \begin{array}{rcl} input & : & \mbox{a natural number } n \\ output & : & \mbox{a list of all nontrivial divisors of } n \end{array} In the problem statement, 1 and :math:`n` are trivial divisors. Let us look at the easier problem first. But *first*, note that we care about clarity and correctness, efficiency concerns come second (if at all). Ingredients in a *first* algorithm: 1. :math:`d` divides :math:`n` if :math:`n~ \% ~d` ``==`` 0 (zero remainder) 2. enumerate all candidate divisors, for :math:`d` ranging between 2 and :math:`n-1`, ``for`` :math:`d` ``in range(2, n)`` 3. append divisor :math:`d` to a list ``L`` using ``L.append`` (:math:`d`) Now we will put the ingredients into a recipe. A flowchart representation of the algorithm is in :numref:`figflowenumdiv`. .. _figflowenumdiv: .. figure:: ./figflowenumdiv.png :align: center Flowchart of the algorithm to enumerate all nontrivial divisors. The Python code in the file ``enumdivs.py`` is in its entirety listed below. :: # MCS 275 L-1 Mon 9 Jan 2017 : enumdivs.py """ enumerate all nontrivial divisors of a number """ NBR = int(input('give a number : ')) FACTORS = [] DIVISOR = 1 for DIVISOR in range(2, NBR): if NBR % DIVISOR == 0: FACTORS.append(DIVISOR) print('nontrivial divisors of %d :' % NBR, FACTORS) Running the script at the command prompt ``$`` can go as follows: :: $ python enumdivs.py give a number : 24 nontrivial divisors of 24 : [2, 3, 4, 6, 8, 12] Even as the code in ``enumdivs.py`` is quite short, there are a few interesting observations to make: * The ``input()`` command returns a string. With ``int()`` we cast the string into an integer. * The command ``range(2, NBR)`` produces the list ``[2,3,..,NBR-1]``. * All code in the same ``for`` or ``if`` must be preceded by same number of spaces. * There are three different occurrences of ``%``: 1. ``NBR % DIVISOR``: remainder after division by ``DIVISOR``, 2. ``%d``: formatting code for decimal output, 3. ``%``: format conversion operator in ``print``. * The ``append`` is a method for the list data type. * *Long* numbers will take a *long* time. Now go back to the original problem, the factorization of a number in primes. We first formulate a solution in words and list the ingredients in a *first* algorithm: 1. We enumerate all candidate divisors, starting at 2. 2. When we find a divisor :math:`d` of :math:`n`, we continue with the quotient :math:`q`, so we assign :math:`n = q`. 3. :math:`(q,r) = \mbox{\tt divmod}(n,d)` computes quotient :math:`q` and remainder :math:`r` of the division of :math:`n` by :math:`d`. 4. All divisors are appended to a list. The flowchart for an algorithm to compute the prime factorization of a natural number is in :numref:`figflowfacprimes`. .. _figflowfacprimes: .. figure:: ./figflowfacprimes.png :align: center Flowchart to factor a natural number in primes. The code in the script ``facnum.py`` is listed below. :: from functools import reduce NBR = int(input('give a natural number n : ')) FACTORS = [] DIVISOR = 2 WORK = NBR while DIVISOR < WORK: (QUOT, REST) = divmod(WORK, DIVISOR) if REST == 0: FACTORS.append(DIVISOR) (WORK, DIVISOR) = (QUOT, 2) else: DIVISOR = DIVISOR + 1 FACTORS.append(WORK) print('factors of ' + str(NBR) + ' :', FACTORS) PRD = reduce(lambda x, y: x*y, FACTORS) print('product of factors :', PRD) In Python 2, the function ``reduce()`` is a built-in function which offers support for functional programming. In Python 3, ``reduce()`` now belongs to the module ``functools`` which must be imported explicitly before usage. The statement :: PRD = reduce(lambda x,y: x*y , FACTORS) computes the product .. math:: p = \prod_{p_i \in L} p_i = p_1 \times p_2 \times \cdots \times p_k. The anonymous function ``lambda x,y: x*y`` is applied to the list ``FACTORS`` *reducing* the list ``FACTORS`` to the product of all its elements. Exercises --------- 1. Use ``facnum.py`` to verify that 2017 is a prime number. What is the next year (following 2017) that is a prime number? 2. Make ``enumdivs.py`` more efficient by examining only :math:`\sqrt{n}` candidate divisors. 3. Relate the number of :math:`n \% d` operations in ``enumdivs.py`` to the number of decimal places of :math:`n`. If we add one more decimal place to the input :math:`n`, how many more operations does ``enumdivs.py`` do? 4. Improve the efficiency of ``facnum.py`` also by taking fewer candidate divisors as in exercise 2 above. 5. Verify the correctness of ``facnum.py`` by an exhaustive enumeration of all numbers :math:`n` within the range :math:`1, \ldots, m`, where :math:`m` is given by the user. 6. Analyze the running time of ``facnum.py`` experimentally by trying various input numbers. For which numbers does ``facnum.py`` take the longest time? Which numbers are easiest?