Due at 11:59pm on 06/30/2015.

## Starter Files

Download lab03.zip. Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.

## Submission

By the end of this lab, you should have submitted the lab with `python3 ok --submit`. You may submit more than once before the deadline; only the final submission will be graded.

• To receive credit for this lab, you must complete Questions 4, 5, and 6 in lab03.py and submit through OK.
• Questions 1, 2, 3, 7, 8, (What Would Python Print?), 9 and 10 (Environment Diagrams) are designed to help introduce concepts and test your understanding.
• Questions 11, 12, and 13 are optional extra practice (all except 11 are in lab03_extra.py). It is recommended that you complete these problems on your own time.

## Higher Order Functions

Higher order functions are functions that take a function as an input, and/or output a function. We will be exploring many applications of higher order functions.

### Question 1: What would Python print?

``````>>> def square(x):
...     return x * x
...
>>> def neg(f, x):
...     return -f(x)
...
>>> neg(square, 4)
______-16``````

### Question 2: What would Python print?

``````>>> def even(f):
...     def odd(x):
...         if x < 0:
...             return f(-x)
...         return f(x)
...     return odd
...
>>> def identity(x):
...     return x
...
>>> triangle = even(identity)
>>> triangle
______<function ...>
>>> triangle(61)
______61
>>> triangle(-4)
______4``````

### Question 3: What would Python print?

``````>>> def first(x):
...     x += 8
...     def second(y):
...         print('second')
...         return x + y
...     print('first')
...     return second
...
>>> f = first(15)
______first
>>> f
______<function ...>
>>> f(16)
______second
39``````

### Question 4: Temperature Converter

Write a function that converts Fahrenheit to Celsius and another function that converts Celsius to Fahrenheit.

The formulas are as follows:

• Celsius x 9 / 5 + 32 = Fahrenheit
• (Fahrenheit - 32) x 5 / 9 = Celsius
``````def f_to_c(fahrenheit):
"""Converts Fahrenheit to Celsius

>>> f_to_c(14)
-10.0
>>> f_to_c(68)
20.0
>>> f_to_c(-31)
-35.0
"""
return (fahrenheit - 32) * 5 / 9
def c_to_f(celsius):
"""Converts Celsius to Fahrenheit

>>> c_to_f(0)
32.0
>>> c_to_f(5)
41.0
>>> c_to_f(-25)
-13.0
"""
return (celsius) * 9 / 5 + 32``````

Use OK to test your code:

``````python3 ok -q f_to_c
python3 ok -q c_to_f``````

### Question 5: Temperature Converters Combined!

Implement `dispatch_function`, which takes in two functions (`f1` and `f2`) and two strings (`option1` and `option2`). `dispatch_function` returns a function that does the following:

• Takes an `option` (a string) and a `number` as its two parameters
• Asserts that `option` is either `option1` or `option2` (using an `assert` statement)
• Calls the corresponding function (`f1` or `f2`) on the given `number`

An `assert` statement checks if a statement is true. If it is false it will raise an error. This is a quick way to check for unexpected inputs. For example, the following `assert` statement ensures `x` won't be zero.

``````def no_zero_division(x):
assert x != 0
return 2 / x``````

If `no_zero_division` is called with `x = 0`, an `AssertionError` occurs:

``````>>> no_zero_division(0):
AssertionError``````
``````def dispatch_function(option1, f1, option2, f2):
"""Takes in two options and two functions. Returns a function that takes in
an option and value and calls either f1 or f2 depending on the given option.

>>> func_d = dispatch_function('c to f', c_to_f, 'f to c', f_to_c)
>>> func_d('c to f', 0)
32.0
>>> func_d('f to c', 68)
20.0
>>> func_d('blabl', 2)
AssertionError
"""
def func(option, value):
assert option == option1 or option == option2
if option == option1:
return f1(value)
else:
return f2(value)
return func``````

Use OK to test your code:

``python3 ok -q dispatch_function``

### Question 6: Flight of the Bumblebee

Write a function that takes in a number `n` and returns a function that takes in a number `range` which will print all numbers from `0` to `range` (including `0` but excluding `range`) but print `Buzz!` instead for all the numbers that are divisible by `n`.

``````def make_buzzer(n):
""" Returns a function that prints numbers in a specified
range except those divisible by n.

>>> i_hate_fives = make_buzzer(5)
>>> i_hate_fives(10)
Buzz!
1
2
3
4
Buzz!
6
7
8
9
"""
def buzz(m):
i = 0
while i < m:
if i % n == 0:
print('Buzz!')
else:
print(i)
i += 1
return buzz``````

Use OK to test your code:

``python3 ok -q make_buzzer``

## Lambdas

`Lambda` expressions are one-line functions that specify two things: the parameters and the return value.

``lambda <parameters>: <return value>``

While both `lambda` and `def` statements are related to functions, there are some differences.

lambda def
Type `lambda` is an expression `def` is a statement
Description Evaluating a `lambda` expression does not create or modify any variables. Lambda expressions just create function objects. Executing a `def` statement will create a new function object and binded to a variable in the current environment.
Example
``````lambda x: x * x
``````
``````def square(x):
return x * x``````

A `lambda` expression by itself is not very interesting. As with any objects such as numbers, booleans, strings, we usually:

• assign lambda to variables (`foo = lambda x: x`)
• pass them in to other functions (`bar(lambda x: x)`)

### Question 7: What Would Python print?

``````>>> a = lambda x: x
>>> a(5) # x is the argument for the lambda function
______5
>>> b = lambda: 3
>>> b()
______3
>>> c = lambda x: lambda: print("123")
>>> c(88)
______function lambda at ...
>>> c(88)()
______123
>>> d = lambda f: f(4) # They can take in functions as well.
>>> def square(x):
...     return x * x
>>> d(square)
______16``````

### Question 8: What would Python print?

``````>>> t = lambda f: lambda x: f(f(f(x)))
>>> s = lambda x: x + 1
>>> t(s)(0)
______3
>>> bar = lambda y: lambda x: 16
>>> bar()(15)
______TypeError: <lambda>() missing 1 required positional argument: 'y'
>>> lambda x: x # Can we access this function?
______<function <lambda> at ...>
>>> foo = lambda: 32
>>> foobar = lambda x,y : x // y
>>> a = lambda x: foobar(foo(), bar(10)(x))
>>> a(2)
______2
>>> b = lambda x,y: print('summer') # When is the body of this function run?
______# Nothing gets printed by the interpreter
>>> c = b(4, 'dog')
______'summer'
>>> print(c)
______None``````

### Question 9: Environment Diagrams with Lambdas

Try drawing environment diagrams for the following code and predicting what Python will output.

You can check your work with the Online Python Tutor. Please try drawing it yourself first!

``````>>> # Part 1
>>> a = lambda x : x * 2 + 1
>>> def b(x):
...     return x * y
...
>>> y = 3
>>> b(y)
______9
>>> def c(x):
...     y = a(x)
...     return b(x) + a(x+y)
...
>>> c(y)
______30``````

### Question 10: More Environment Diagrams with Lambdas

Try drawing environment diagrams for the following code and predicting what Python will output.

You can check your work with the Online Python Tutor. Please try drawing it yourself first!

``````>>> # This one is pretty tough. A carefully drawn environment
>>> # diagram will be really useful.
>>> g = lambda x: x + 3
>>> def wow(f):
...     def boom(g):
...       return f(g)
...     return boom
...
>>> f = wow(g)
>>> f(2)
______5
>>> g = lambda x: x * x
>>> f(3)
______6``````

## Extra Questions

Questions in this section are not required for submission. However, we encourage you to try them out on your own time for extra practice.

### Question 11: Lambdas and Currying

We can transform multiple-argument functions into a chain of single-argument, higher order functions by taking advantage of lambda expressions. This is useful when dealing with functions that take only single-argument functions. We will see some examples of these later on.

Write a function `lambda_curry2` that will curry any two argument function using lambdas. See the doctest if you're not sure what this means.

``````def lambda_curry2(func):
"""
Returns a Curried version of a two argument function func.
>>> y = x(3)
>>> y(5)
8
"""
return ______
return lambda arg1: lambda arg2: func(arg1, arg2)``````

Use OK to test your code:

``python3 ok -q lambda_curry2``

### Question 12: Funception

Write a function (funception) that takes in another function `func_a` and a number `start` and returns a function (`func_b`) that will have one parameter to take in the stop value. `func_b` should take the following into consideration the following in order:

1. Takes in the stop value.
2. If the value of `start` is less than 0, it should exit the function.
3. If the value of `start` is greater than stop, apply `func_a` on `start` and return the result.
4. If not, apply `func_a` on all the numbers from start (inclusive) up to stop (exclusive) and return the product.
``````def funception(func_a, start):
""" Takes in a function (function A) and a start value.
Returns a function (function B) that will find the product of
function A applied to the range of numbers from
start (inclusive) to stop (exclusive)

>>> def func_a(num):
...     return num + 1
>>> func_b1 = funception(func_a, 3)
>>> func_b1(2)
4
>>> func_b2 = funception(func_a, -2)
>>> func_b2(-3)
>>> func_b3 = funception(func_a, -1)
>>> func_b3(4)
>>> func_b4 = funception(func_a, 0)
>>> func_b4(3)
6
>>> func_b5 = funception(func_a, 1)
>>> func_b5(4)
24
"""
def func_b(stop):
i = start
product = 1
if start < 0:
return None
if start > stop:
return func_a(start)
while i < stop:
product *= func_a(i)
i += 1
return product
return func_b``````

Use OK to test your code:

``python3 ok -q funception``

### Question 13: I Heard You Liked Functions...

Define a function `cycle` that takes in three functions `f1`, `f2`, `f3`, as arguments. `cycle` will return another function that should take in an integer argument `n` and return another function. That final function should take in an argument `x` and cycle through applying `f1`, `f2`, and `f3` to `x`, depending on what `n` was. Here's the what the final function should do to `x` for a few values of `n`:

• `n = 0`, return `x`
• `n = 1`, apply `f1` to `x`, or return `f1(x)`
• `n = 2`, apply `f1` to `x` and then `f2` to the result of that, or return `f2(f1(x))`
• `n = 3`, apply `f1` to `x`, `f2` to the result of applying `f1`, and then `f3` to the result of applying `f2`, or `f3(f2(f1(x)))`
• `n = 4`, start the cycle again applying `f1`, then `f2`, then `f3`, then `f1` again, or `f1(f3(f2(f1(x))))`
• And so forth.

Hint: most of the work goes inside the most nested function.

``````def cycle(f1, f2, f3):
""" Returns a function that is itself a higher order function
...     return x + 1
>>> def times2(x):
...     return x * 2
...     return x + 3
>>> identity = my_cycle(0)
>>> identity(5)
5
4
>>> do_all_functions = my_cycle(3)
>>> do_all_functions(2)
9
>>> do_more_than_a_cycle = my_cycle(4)
>>> do_more_than_a_cycle(2)
10
>>> do_two_cycles = my_cycle(6)
>>> do_two_cycles(1)
19
"""
def ret_fn(n):
def ret(x):
i = 0
while i < n:
if i % 3 == 0:
x = f1(x)
elif i % 3 == 1:
x = f2(x)
else:
x = f3(x)
i += 1
return x
return ret
return ret_fn``````

Use OK to test your code:

``python3 ok -q cycle``