<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Functional Programming in Python <center>
<p>
<p>
Shagun Sodhani
# Functions as first class citizens
Step1:  Lambda is another way of defining a function
Higher Order Functions
Step2:  Why would I want something like this?
A Familiar Pattern
Step3:  Operators to the rescure
Step4:  Lets do some maths
Number of transform functions = m
Number of reduce functions = n
Number of functions in the first workflow = m*n
Number of functions in the second workflow = m + n
Write small, re-useable function
Step5:  Function returns Function
Step6:  Partial Functions
Step7:  debug("Error 404") = partial(logger, "debug")("Error 404")
Step8:  Currying
f(a, b, c) => g(a)(b)(c)
Step9:  Currying gets you specialized functions from more general functions
Map, Reduce, Filter
An alternate view to iteration
Step10:  Benefits
Functional
One-liner
Elemental operations
itertools — Functions creating iterators for efficient looping
Step11:  Recursion
Step12:  Sadly, no tail recursion
Comprehension
Step13:  Generators
Step14:  Pipelines
Sequence of Operations
Step15:  No Modularity
Step16:  f(g(h(i(...x)))
Modular but Ugly
<ASSISTANT_TASK:>
Python Code:
def mul(a, b):
    return a*b
mul(2, 3)
mul = lambda a, b: a*b
mul(2, 3)
Explanation: Functional Programming in Python <center>
<p>
<p>
Shagun Sodhani
# Functions as first class citizens
End of explanation
mul(mul(2, 3), 3)
def transform_and_add(func, a, b):
    return func(a) + func(b)
transform_and_add(lambda x: x**2, 1, 2)
Explanation: Lambda is another way of defining a function
Higher Order Functions
End of explanation
def square_and_add(a, b):
    return (a**2 + b**2)
def cube_and_add(a, b):
    return (a**3 + b**3)
def quad_and_add(a, b):
    return (a**4 + b**4)
print(square_and_add(1, 2))
print(cube_and_add(1, 2))
print(quad_and_add(1, 2))
square = lambda x: x**2
cube = lambda x: x**3
quad = lambda x: x**4
print(square_and_add(1, 2) == transform_and_add(square, 1, 2))
print(cube_and_add(1, 2) == transform_and_add(cube, 1, 2))
print(quad_and_add(1, 2) == transform_and_add(quad, 1, 2))
def square_and_add(a, b):
    return (a**2 + b**2)
def cube_and_mul(a, b):
    return ((a**3) * (b**3))
def quad_and_div(a, b):
    return ((a**4) / (b**4))
print(square_and_add(1, 2))
print(cube_and_mul(1, 2))
print(quad_and_div(1, 2))
def transform_and_reduce(func_transform, func_reduce, a, b):
    return func_reduce(func_transform(a), func_transform(b))
print(square_and_add(1, 2) == transform_and_reduce(square, lambda x, y: x+y, 1, 2))
print(cube_and_mul(1, 2) == transform_and_reduce(cube, lambda x, y: x*y, 1, 2))
print(quad_and_div(1, 2) == transform_and_reduce(quad, lambda x, y: x/y, 1, 2))
Explanation: Why would I want something like this?
A Familiar Pattern
End of explanation
import operator
print(square_and_add(1, 2) == transform_and_reduce(square, operator.add, 1, 2))
print(cube_and_mul(1, 2) == transform_and_reduce(cube, operator.mul, 1, 2))
print(quad_and_div(1, 2) == transform_and_reduce(quad, operator.truediv, 1, 2))
Explanation: Operators to the rescure
End of explanation
print(square_and_add(1, 2) == transform_and_reduce(lambda x: x**2, lambda x, y: x+y, 1, 2))
print(cube_and_mul(1, 2) == transform_and_reduce(lambda x: x**3, lambda x, y: x*y, 1, 2))
print(quad_and_div(1, 2) == transform_and_reduce(lambda x: x**4, lambda x, y: x/y, 1, 2))
Explanation: Lets do some maths
Number of transform functions = m
Number of reduce functions = n
Number of functions in the first workflow = m*n
Number of functions in the second workflow = m + n
Write small, re-useable function
End of explanation
from time import time
def timer(func):
    def inner(*args, **kwargs):
        t = time()
        func(*args, **kwargs)
        print("Time take = {time}".format(time = time() - t))
    return inner
def echo_func(input):
    print(input)
    
timed_echo = timer(echo_func)
timed_echo(1000000)
Explanation: Function returns Function
End of explanation
def logger(level, message):
    print("{level}: {message}".format(level = level, message = message))
def debug(message):
    return logger("debug", message)
def info(messgae):
    return logger("info", message)
debug("Error 404")
from functools import partial
debug = partial(logger, "debug")
info = partial(logger, "info")
debug("Error 404")
Explanation: Partial Functions
End of explanation
partial(logger, "debug")("Error 404")
Explanation: debug("Error 404") = partial(logger, "debug")("Error 404")
End of explanation
def transform_and_add(func_transform, a, b):
    return func_transform(a) + func_transform(b)
def curry_transform_and_add(func_transform):
    def apply(a, b):
        return func_transform(a) + func_transform(b)
    
    return apply
print(transform_and_add(cube, 1, 2) == curry_transform_and_add(cube)(1, 2))
Explanation: Currying
f(a, b, c) => g(a)(b)(c)
End of explanation
input_list = [1, 2, 3, 4]
squared_list = map(lambda x: x**2, input_list)
print(type(squared_list))
print(next(squared_list))
print(next(squared_list))
from functools import reduce
sum_list = reduce(operator.add, input_list)
print(sum_list)
sum_squared_list = reduce(operator.add, 
                          map(lambda x: x**2, input_list))
print(sum_squared_list)
even_list = list(
                filter(lambda x: x%2==0, input_list))
sum_even_list = reduce(operator.add, even_list)
print(sum_even_list)
print(reduce(operator.add,
            (map(lambda x: x**2, 
                filter(lambda x: x%2==0, input_list)))))
Explanation: Currying gets you specialized functions from more general functions
Map, Reduce, Filter
An alternate view to iteration
End of explanation
from itertools import accumulate
acc = accumulate(input_list, operator.add)
print(input_list)
print(type(acc))
print(next(acc))
print(next(acc))
print(next(acc))
Explanation: Benefits
Functional
One-liner
Elemental operations
itertools — Functions creating iterators for efficient looping
End of explanation
def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)
Explanation: Recursion
End of explanation
print(input_list)
collection = list()
is_even = lambda x: x%2==0
for data in input_list:
    if(is_even(data)):
        collection.append(data)
    else:
        collection.append(data*2)
print(collection)
collection = [data if is_even(data) else data*2 
              for data in input_list]
print(collection)
Explanation: Sadly, no tail recursion
Comprehension
End of explanation
collection = (data if is_even(data) else data*2 
              for data in input_list)
print(collection)
Explanation: Generators
End of explanation
def pipeline_each(data, fns):
    return reduce(lambda a, x: map(x, a),
                  fns,
                  data)
import re
strings_to_clean = ["apple https://www.apple.com/",
                    "google https://www.google.com/",
                    "facebook https://www.facebook.com/"]
def format_string(input_string):
    return re.sub(r"http\S+", "", input_string).strip().title()
for _str in map(format_string, strings_to_clean):
    print(_str)
Explanation: Pipelines
Sequence of Operations
End of explanation
import re
def remove_url(input_string):
    return re.sub(r"http\S+", "", input_string).strip()
def title_case(input_string):
    return input_string.title()
def format_string(input_string):
    return title_case(remove_url(input_string))
for _str in map(format_string, strings_to_clean):
    print(_str)
Explanation: No Modularity
End of explanation
import re
for _str in pipeline_each(strings_to_clean, [remove_url,
                                            title_case]):
    print(_str)
Explanation: f(g(h(i(...x)))
Modular but Ugly
End of explanation
<END_TASK>