=============== Lists and Loops =============== Introduction ------------ Lists provide a way to represent ordered sequences of data. They are an essential part of programming in Python and you will use them repeatedly in your work. Loops are a powerful building block for writing programs. They make it possible to write programs that perform repeated work, without having to make copies of the same instructions (lines of code). Loops also make programs easier to read and maintain. They enable us to decide how many times to iterate while a program is running (for instance, based on a calculation or something the user enters), rather than at the time we wrote the program. In fact, it would be impossible to write most programs without loops. In this lab, you will work with lists and loops. By the end of the lab, you should be able to: - do basic operations on lists - use for-loops to write repeated code - write code to construct new lists As an example, we're also going to look at how to plot and integrate real-valued functions. Getting started --------------- .. include:: includes/getting-started-labs.txt Once you have collected the lab materials, navigate to the ``lab2`` directory and fire up ``ipython``. Lists ----- Before you get started on this section, please run ``list_examples.py`` in ipython:: In[4]: run list_examples.py (The text ``In[x]:``, where x is an integer, is the iPython prompt.) This file contains a few lists (``l0``, ``l1``, and ``l2``) that you will use in the tasks for this section. You can see the values of each list just by typing their names in ipython (try doing this now to make sure you've loaded ``list_examples.py`` correctly):: In [5]: l0 Out[5]: [] In [6]: l1 Out[6]: [1, 'abc', 5.7, [1, 3, 5]] In [7]: l2 Out[7]: [10, 11, 12, 13, 14, 15, 16] Lists provide a way to represent ordered sequences of data. They are an essential part of programming in Python and you will use them repeatedly in your work. In this section, you will practice basic list operations. Each task has one or more links to discussions of the concepts needed to complete the task. Try doing the tasks before you review the concepts. #. [literals_] Create a list that contains the values 7, "xyz", and 2.7. #. [length_] Compute the length of list ``l1``. #. [indexing_] Write expressions to retrieve the value ``5.7`` from list ``l1`` and to retrieve the value 5 from the third element of ``l1``. #. [indexing_] Predict what will happen if you evaluate the expression ``l1[4]`` and then try it out. #. [indexing_] Predict what happens if you evaluate the expression ``l2[-1]`` and then try it out. #. [indexing_] Write a statement to change the value ``3`` in the third element of ``l1`` to ``15.0``. #. [slicing_] Write an expression to create a slice containing the second through fifth elements (inclusive) of list ``l2``. #. [slicing_] Write an expression to create a slice containing the first three elements of list ``l2``. #. [slicing_] Write an expression to create a slice containing the second through last elements of list ``l2``. #. [operations_] Write code to add four elements to list ``l0`` using the ``append`` operation and then extract the third element. How many appends do you need to do? #. [operations_] Create a new list ``nl`` by concatenating the resulting value of ``l0`` with ``l1`` and then update an element of ``nl``. Do either ``l0`` or ``l1`` change as a result executing these statements? Loops ----- Loops provide a mechanism for repeatedly performing a computation. They are often used in conjunction with lists in Python. As in the last section, this section contains a collection of tasks with links to discussions of the necessary concepts. The list tasks were simple enough that you could easily type the solutions into ipython directly The solutions to the tasks in this section have multiple lines and so you might want to use an editor and put the code in a file (feel free to use ``list_examples.py`` for this purpose). **Remember to save any changes that you make to the file and re-run it in ipython.** #. [basics_] Write a loop to compute a variable ``all_pos`` that has the value ``True`` if all of the elements in the list ``l3`` are positive and ``False`` otherwise. #. [`loops & append`_] Write code to create a new list that contains only the positive values in the list ``l3``. #. [`loops & append`_] Write code that uses append to create a new list ``nl`` in which the ith element of ``nl`` has the value ``True`` if the ith element of ``l3`` has a positive value and ``False`` otherwise #. [range_, `list initialization`_] Write code that uses range and list initialization to create a new list ``nl`` in which the ith element of ``nl`` is ``True`` if the ith element of ``l3`` has a positive value and ``False`` otherwise. Hint: start by calculating a list of the right length with ``False`` at every index. #. [range_, `list initialization`_] Given a list ``l4`` that contains values in the range from ``0`` to ``M-1`` inclusive (``M`` is predefined), write code that creates a new list in which the ith element contains a count of the number of times the value i occurred in ``l4``. Integration ----------- Now that you have some practice with loops, we move on to a more realistic example. .. image:: img/integration_plot.svg :align: center In this section we will compute definite integrals using numeric quadrature. The definite integral of a function is just the area under the curve of that function between two x values. We can calculate this area by filling in the curve with many small rectangles and then adding up the area of each of the rectangles - this method is aptly named `the rectangle method `_. In the file ``integration_lab.py`` there is a function .. code:: def f(x): return x*x This code defines a new function (like ``print`` or ``math.cos``) that takes a floating point value as an input and produces a floating point value as an output. In this sense it is a mathematical function like sin or log. ``f`` corresponds to the mathematical function :math:`f(x) = x^2.` We will use a for loop to compute the integral of this function from 0 to 1 using N rectangles. .. math:: \int_0^1 f(x) \,\mathrm{d}x = \int_0^1 x^2 \,\mathrm{d}x We have defined a function ``integrate`` that you will use for this task. Add code to ``integrate`` to perform the following steps. #. Decide on a number of rectangles ``N`` (10 is a good number to start). #. Compute the width (``dx``) of your rectangles. #. Create a ``totalArea`` variable to store the sum of the areas of all the rectangles. Start it at zero. #. Make a loop that takes a variable, ``i``, from 0 to N. #. For each of these steps compute the area of the rectangle as height*width. Height is the value equal to the ``f`` function called on each ``i*dx`` and width is ``dx``. Add this area to ``totalArea``. #. Once the for loop has finished, print the value to the screen. #. Celebrate, you have just replaced calculus. Errors ~~~~~~ The value of this integral is .. math:: \int_0^1 x^2 \,\mathrm{d}x = \left[\frac{x^3}{3}\right]_0^1 = \frac{1^3}{3} - \frac{0}{3} = \frac{1}{3} = .33333333... Did your code compute the correct value? Try your program again but set the number of rectangles to 100 instead. Try 1000. How many rectangles do you have to use to obtain a result that is correct enough? A quick lesson in abstraction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Changing the value of ``N`` in your function is tedious. Having multiple copies of your function for different values values of ``N`` is a very bad idea. To fix this problem, we can change the definition of ``integrate`` to take the number of rectangles as an argument named ``N`` instead. To see how this approach works, change the header for ``integrate`` to: .. code:: def integrate(N): and then remove the line that initializes the value of ``N``. Re-run ``integration_lab.py`` in ipython and then call integrate with different values for ``N`` (``integrate(10)`` or ``integrate(10000)``, for example). As you write code over the course of the term, you will want to look for these types of opportunities to exploit abstraction to create more general functions and reduce the amount of duplicated code in your programs. Plotting functions ------------------ .. image:: img/example_plot.svg :align: center In this section we will plot the *sinc* function, which is defined as follows: .. math:: \mathrm{sinc}(x) = \frac{\sin(x)}{x} Open the file ``plot_lab.py`` and you will see that we have already defined this function for you under the name ``sinc``. This function is similar to ``f`` above, in that it is again a function in the mathematical sense, the sort that we can draw on a standard two dimensional plot as can be seen above. Python has a very useful library named ``pylab`` that we can use to plot data. In particular, we'll be using a fuction aptly named ``pylab.plot`` that can help us here. This function takes a list of floats with the x values and a list of floats with the y values as arguments, and produces an image of the corresponding x and y points plotted on a standard axis. We'll also use the function ``pylab.show``, which does not take any arguments, to display the plot. In order to see what the sinc function looks like we will need to create lists of x and y values and then give these lists to the plot function. Part 1 ~~~~~~ In the function ``plot_sinc``: #. Create and fill ``X`` with the values ``-10, -9, -8, ..., 9, 10`` using the ``range`` function. #. Create an empty list ``Y``. #. Use a for loop to fill the ``Y`` list with values equal to the ``sinc`` function called on each of the ``X`` values. #. Call ``pylab.plot`` with ``X`` and ``Y`` inputs and then call ``pylab.show()`` to show the plot. Part 2 ~~~~~~ The function is not clear from this image. The distance between points is too large. Instead of filling ``X`` with values ``-10, -9, ...`` make a list larger filled with values that are closer together like ``-10, -9.9, -9.8, -9.7, ..., -0.1, 0, 0.1, 0.2, 0.3, ..., 9.9, 10``. The ``range`` function, which you likely used in Part 1, generates integer values, but we'd like to use floating point values for the x values instead. Fortunately, there is an analogous library function ``numpy.arange`` for floating point values. It takes the same arguments as range (lower bound, upper bound, increment), but as floats rather than integers and it generates floats instead of integers. Change your code to: #. use ``numpy.arange`` instead of ``range`` in the computation of ``X`` and #. take the increment as an argument to ``plot_sinc`` rather than using a hard-coded value. Then: #. Plot the function with spacing between x points equal to .1 by calling ``plot_sinc(.1)``. #. Plot the function with spacing between x points equal to .01 by calling ``plot_sinc(.01)``. When finished ------------- .. include:: includes/finished-labs-1.txt .. code:: git add list_examples.py git add integration_lab.py git add plot_lab.py git commit -m "Finished with lab2" git push .. include:: includes/finished-labs-2.txt .. _literals: lists.html#literals .. _length: lists.html#length .. _indexing: lists.html#indexing .. _slicing: lists.html#slicing .. _operations: lists.html#ops .. _list initialization: lists.html#list-initialization .. _basics: loops.html#basics .. _loops & append: loops.html#append .. _range: loops.html#range