#!/usr/bin/env python
# coding: utf-8
# # Introduction to programming for Geoscientists through Python
# ### [Gerard Gorman](http://www.imperial.ac.uk/people/g.gorman), [Nicolas Barral](http://www.imperial.ac.uk/people/n.barral)
#
# # Lecture 2: Conditional expressions, loops and lists
# Learning objectives:
#
# * Know how to form a *condition* using a *boolean expression*.
# * Be able to use a conditional expression in combination with a *while-loop* to perform repetitive tasks.
# * Be able to store data elements within a Python *list*.
# * Be able to use a *for-loop* to iterate, and perform some task, over a *list* of elements.
# ## Boolean expressions
# An expression with value *true* or *false* is called a boolean expression. Example expressions for what you would write mathematically as
# $C=40$, $C\ne40$, $C\ge40$, $C\gt40$ and $C\lt40$ are:
#
# ```python
# C == 40 # Note: the double == checks for equality!
# C != 40 # This could also be written as 'not C == 4'
# C >= 40
# C > 40
# C < 40
# ```
# We can test boolean expressions in a Python shell:
# In[1]:
C = 41
print("C != 40: ", C != 40)
print("C < 40: ", C < 40)
print("C == 41: ", C == 41)
# Several conditions can be combined with the special 'and' and 'or' keywords into a single boolean expression:
#
# * Rule 1: (**C1** *and* **C2**) is *True* only if both **C1** and **C2** are *True*
# * Rule 2: (**C1** *or* **C2**) is *True* if either **C1** or **C2** are *True*
#
# Examples:
# In[2]:
x=0; y=1.2
print (x >= 0 and y < 1)
# ## Exercise 2.1: Values of boolean expressions
# Add a comment to the code below to explain the outcome of each of the boolean expressions:
# In[3]:
C = 41
print("Case 1: ", C == 40)
print("Case 2: ", C != 40 and C < 41)
print("Case 3: ", C != 40 or C < 41)
print("Case 4: ", not C == 40)
print("Case 5: ", not C > 40)
print("Case 6: ", C <= 41)
print("Case 7: ", not False)
print("Case 8: ", True and False)
print("Case 9: ", False or True)
print("Case 10: ", False or False or False)
print("Case 11: ", True and True and False)
print("Case 12: ", False == 0)
print("Case 13: ", True == 0)
print("Case 14: ", True == 1)
# ## Loops
# Suppose we want to make a table of Celsius and Fahrenheit degrees:
# ```
# -20 -4.0
# -15 5.0
# -10 14.0
# -5 23.0
# 0 32.0
# 5 41.0
# 10 50.0
# 15 59.0
# 20 68.0
# 25 77.0
# 30 86.0
# 35 95.0
# 40 104.0
# ```
# How do we write a program that prints out such a table?
# 
# We know from the last lecture how to make one line in this table:
# In[4]:
C = -20
F = 9.0/5*C + 32
print(C, F)
# We can just repeat these statements:
# In[5]:
C=-20; F=9.0/5*C+32; print(C,F)
C=-15; F=9.0/5*C+32; print(C,F)
C=-10; F=9.0/5*C+32; print(C,F)
C=-5; F=9.0/5*C+32; print(C,F)
C=0; F=9.0/5*C+32; print(C,F)
C=5; F=9.0/5*C+32; print(C,F)
C=10; F=9.0/5*C+32; print(C,F)
C=15; F=9.0/5*C+32; print(C,F)
C=20; F=9.0/5*C+32; print(C,F)
C=25; F=9.0/5*C+32; print(C,F)
C=30; F=9.0/5*C+32; print(C,F)
C=35; F=9.0/5*C+32; print(C,F)
C=40; F=9.0/5*C+32; print(C,F)
# So we can see that works but its **very boring** to write and very easy to introduce a misprint.
#
# **You really should not be doing boring repetitive tasks like this.** Spend one time instead looking for a smarter solution. When programming becomes boring, there is usually a construct that automates the writing. Computers are very good at performing repetitive tasks. For this purpose we use **loops**.
# ## The while loop (and the significance of indentation)
# A while loop executes repeatedly a set of statements as long as a **boolean** (i.e. *True* / *False*) condition is *True*
#
# ```
# while condition:
#
#
# ...
#
# ```
# Note that all statements to be executed within the loop must be indented by the same amount! The loop ends when an unindented statement is encountered.
#
# At this point it is worth noticing that **blank spaces may or may not be important** in Python programs. These statements are equivalent (blanks do not matter):
# In[6]:
v0=3
v0 = 3
v0= 3
# The computer does not care but this formatting style is
# considered clearest for the human reader.
v0 = 3
# Here is a while loop example where blank spaces really do matter:
# In[7]:
counter = 0
while counter <= 10:
counter = counter + 1
print(counter)
# Let's take a look at what happens when we forget to indent correctly:
# In[8]:
counter = 0
while counter <= 10:
counter = counter + 1
print(counter)
# Let's use the while loop to create the table above:
# In[9]:
C = -20 # Initialise C
dC = 5 # Increment for C within the loop
while C <= 40: # Loop heading with condition
F = (9.0/5)*C + 32 # 1st statement inside loop
print(C, F) # 2nd statement inside loop
C = C + dC # Increment C for the next iteration of the loop.
# ## Exercise 2.2: Make a Fahrenheit-Celsius conversion table
# Write a program that prints out a table with Fahrenheit degrees 0, 10, 20, ..., 100 in the first column and the corresponding Celsius degrees in the second column.
#
# Hint: $C = \frac{5}{9}(F-32)$
# In[ ]:
# ## Exercise 2.3: Write an approximate Fahrenheit-Celsius conversion table
# Many people use an approximate formula for quickly converting Fahrenheit ($F$) to Celsius ($C$) degrees:
# $C \approx \hat{C} = \frac{F − 30}{2}$
# Modify the program from the previous exercise so that it prints three columns: $F$, $C$, and the approximate value $\hat{C}$.
# In[ ]:
# ## Lists
# So far, one variable has referred to one number (or string). Sometimes however we naturally have a collection of numbers, say
# degrees −20, −15, −10, −5, 0, ..., 40. One way to store these values in a computer program would be to have one variable per value, i.e.
# In[10]:
C1 = -20
C2 = -15
C3 = -10
# ...
C13 = 40
# This is clearly a terrible solution, particularly if we have lots of values. A better way of doing this is to collect values together in a list:
# In[11]:
C = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
# Now there is just one variable, **C**, holding all the values. Elements in a list are accessed via an index. List indices are always numbered as 0, 1, 2, and so forth up to the number of elements minus one:
# In[12]:
mylist = [4, 6, -3.5]
print(mylist[0])
print(mylist[1])
print(mylist[2])
print(len(mylist)) # length of list
# Here are a few example of operations that you can perform on lists:
# In[13]:
C = [-10, -5, 0, 5, 10, 15, 20, 25, 30]
C.append(35) # add new element 35 at the end
print(C)
# In[14]:
C=C+[40,45] # And another list to the end of C
print(C)
# In[15]:
C.insert(0, -15) # Insert -15 as index 0
print(C)
# In[16]:
del C[2] # delete 3rd element
print(C)
# In[17]:
del C[2] # delete what is now 3rd element
print(C)
# In[18]:
print(len(C)) # length of list
# In[19]:
print(C.index(10)) # Find the index of the list with the value 10
# In[20]:
print(10 in C) # True only if the value 10 is stored in the list
# In[21]:
print(C[-1]) # The last value in the list.
# In[22]:
print(C[-2]) # The second last value in the list.
# You can also extract sublists using ":"
# In[23]:
print(C[5:]) # From index 5 to the end of the list.
# In[24]:
print(C[5:7]) # From index 5 up to, but not including index 7.
# In[25]:
print(C[7:-1]) # From index 7 up to the second last element.
# In[26]:
print(C[:]) # [:] specifies the whole list.
# You can also unpack the elements of a list into seperate variables:
# In[27]:
somelist = ['Curly', 'Larry', 'Moe']
stooge1, stooge2, stooge3 = somelist
print(stooge3, stooge2, stooge1)
# ## Exercise 2.4: Store odd numbers in a list
#
# Step 1: Write a program that generates all odd numbers from 1 to *n*. Set *n* in the beginning of the program and use a while loop to compute the numbers. (Make sure that if *n* is an even number, the largest generated odd number is *n*-1.).
#
# Step 2: Store the generated odd numbers in a list. Start with an empty list and use the same while loop where you generate each odd number, to append the new number to the list.
#
# Finally, print the list elements to the screen.
# In[ ]:
# ## For loops
# We can visit each element in a list and process the element with some statements using a *for* loop, for example:
# In[28]:
degrees = [0, 10, 20, 40, 100]
for C in degrees:
print('list element:', C)
print('The degrees list has', len(degrees), 'elements')
# Notice again how the statements to be executed within the loop must be indented! Let's now revisit the conversion table example using the *for* loop:
# In[29]:
Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
for C in Cdegrees:
F = (9.0/5)*C + 32
print(C, F)
# We can easily beautify the table using the printf syntax that we encountered in the last lecture:
# In[30]:
for C in Cdegrees:
F = (9.0/5)*C + 32
print('%5d %5.1f' % (C, F))
# It is also possible to rewrite the *for* loop as a *while* loop, i.e.,
#
# ```
# for element in somelist:
# # process element
# ```
# can always be transformed to a *while* loop
# ```
# index = 0
# while index < len(somelist):
# element = somelist[index]
# # process element
# index += 1
# ```
# Taking the previous table example:
# In[31]:
Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
index = 0
while index < len(Cdegrees):
C = Cdegrees[index]
F = (9.0/5)*C + 32
print('%5d %5.1f' % (C, F))
index += 1
# Rather than just printing out the Fahrenheit values, let's also store these computed values in a list of their own:
# In[32]:
Cdegrees = [-20, -15, -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40]
Fdegrees = [] # start with empty list
for C in Cdegrees:
F = (9.0/5)*C + 32
Fdegrees.append(F) # add new element to Fdegrees
print(Fdegrees)
# In Python *for* loops usually loop over list values (elements), i.e.,
#
# ```
# for element in somelist:
# ...process variable element
# ```
# However, we can also loop over list indices:
#
# ```
# for i in range(0, len(somelist), 1):
# element = somelist[i]
# ... process element or somelist[i] directly
# ```
# The statement *range(start, stop, inc)* generates a list of integers *start*, *start+inc*, *start+2\*inc*, and so on up to, but not including, *stop*. We can also write *range(stop)* as an abbreviation for *range(0, stop, 1)*:
# In[33]:
print(range(3)) # same as range(0, 3, 1)
# In[34]:
print(range(2, 8, 3))
# ## List comprehensions
# Consider this example where we compute two lists in a *for* loop:
# In[35]:
n = 16
Cdegrees = []; Fdegrees = [] # empty lists
for i in range(n):
Cdegrees.append(-5 + i*0.5)
Fdegrees.append((9.0/5)*Cdegrees[i] + 32)
print("Cdegrees = ", Cdegrees)
print("Fdegrees = ", Fdegrees)
# As constructing lists is a very common requirement, the above way of doing it can become very tedious to both write and read. Therefore, Python has a compact construct, called list comprehension for generating lists from a *for* loop:
# In[36]:
n = 16
Cdegrees = [-5 + i*0.5 for i in range(n)]
Fdegrees = [(9.0/5)*C + 32 for C in Cdegrees]
print("Cdegrees = ", Cdegrees)
print("Fdegrees = ", Fdegrees)
# The general form of a list comprehension is:
# ```
# somelist = [expression for element in somelist]
# ```
# ## Exercise 2.5: Repeat the previous exercise using: a for loop, list comprehension and the range function
# In[37]:
# Using for loop
# In[38]:
# Using list comprehension
# In[39]:
# Using the function range