<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 [0] Functions
1 [0] Defining
Step1:  2 [0] Running (calling)
Step2:  3 [0] Using parameters
Step3:  [0] 3.1 Arguments are passed by assignment (by value, in C)
Arguments changed in functions are local.
Step4:  [0] 3.2 To modify an argument (call by reference, in C)
Arguments changed in functions never are global ... so, we need to return them (if we want to see that changes).
Step5:  Containers are passed to functions by reference.
Step6:  3.3 [1] Parameters can be optional
Step7:  3.4 [1] Working with an undefined number of unnamed parameters
Step8:  3.5 [1] Working with an undefined number of named parameters
Step9:  3.6 [1] Functions can be arguments to functions
Step10:  3.7 [1] Of course, functions are objects!
Functions are objects which can, for example, be "copied" to other objects
Step11:  4 [0] Recursion
Call yourself!
Step12:  5 [1] Nesting functions
Step13:  6 [1] Decorating functions
Extending the behavior of functions that we don't want (or cannot) to modify
Step14:  The same example using a decorator
Step15:  7 [1] Lambda functions
$\lambda$-funcions are "anonymous" functions
Step16:  Lambda functions are useful because they can be defined inline and we don't need give a name to use them
Step17:  7.1 Filtering data with $\lambda$-functions
... and filter() ...
Step18:  In other words ..., filter() creates a sequences of elements for which a function returns true.
Step19:  7.2 Mapping data with $\lambda$-functions
... and map() ...
Step20:  In other words ..., map() applies a $\lambda$-function to all the items in an input sequence.
Step21:  7.3 Reducing data with $\lambda$-functions
Step22:  In other words ..., reduce() process iteratively a sequence of items. One of them is item of the sequence and the other, the previus output of the reduce().
Step23:  Only to see the performance
Step24:  Another interesting example
Step25:  Now using $\lambda$-functions
Step26:  8 [0] Namespaces and scopes
8.1 [0] Names
Python objects are referenced by names
Step27:  Different objects have different id's
Step28:  A namespace is a collection of names. All packages, modules, classes and functions define their own namespace (basically, the variables -- or names -- locally defined). The scope of a name is the region of code where that name can be referenced without using any prefix.
8.2 [0] Functions create their own namespace
(Please, restart this kernel) Do you think that the second print(a) of in the following code should work?
Step29:  [0] 8.3 Names of outter scopes are accessible in inner ones
Step30:  8.4 [0] When there is a coincidence, local scopes create their own names
Step31:  8.5 [1] To avoid creating a new name when there is a coincidence, use global
However, this is DISCOURAGED. It is confusing and isn’t thread-safe (among other things).
Step32:  It's better to do
<ASSISTANT_TASK:>
Python Code:
def say_hello():
    print('Hello world!')
Explanation: [0] Functions
1 [0] Defining
End of explanation
say_hello() 
Explanation: 2 [0] Running (calling)
End of explanation
def max(a, b):
    if a > b:
        return a
    else:
        return b
max(1,2)
Explanation: 3 [0] Using parameters
End of explanation
def func(a):
    a = 2
x = 1
func(x)
print(x)
Explanation: [0] 3.1 Arguments are passed by assignment (by value, in C)
Arguments changed in functions are local.
End of explanation
def func(a,b):
    a += 1
    b -= 1
    return a,b
x = 1
y = 2
x, y = func(x, y)
print(x,y)
Explanation: [0] 3.2 To modify an argument (call by reference, in C)
Arguments changed in functions never are global ... so, we need to return them (if we want to see that changes).
End of explanation
def func(l):
    print(l)
    del l[0]
l = [1,2]
func(l)
print(l)
Explanation: Containers are passed to functions by reference.
End of explanation
def optional_args(a=1, b=2):
    print(a, b)
    
optional_args()
optional_args(3)
optional_args(4,3)
optional_args(b=3, a=4)
optional_args(b=3)
# Positional parameters must go before than optinal parameters
def func(a, b=2):
    print(a,b)
    
func(1)
func(1,3)
func(b=2)
Explanation: 3.3 [1] Parameters can be optional
End of explanation
def variable_args(*vargs):
    print(vargs)
    print(type(vargs))
    for i in vargs:
        print(i)
    print('first argument =', vargs[0])
    
variable_args("hola", "caracola", ("hi", "folks"))
Explanation: 3.4 [1] Working with an undefined number of unnamed parameters
End of explanation
def keyworded_args(**kargs):
    print(kargs)
    print(type(kargs))
    for i in kargs:
        print(i, kargs[i])
    print("'a' argument =", kargs['a'])
    
keyworded_args(a=1, b='a')
Explanation: 3.5 [1] Working with an undefined number of named parameters
End of explanation
def add(x, y):
    return x + y
def compute(function, x, y):
    return function(x, y)
compute(add, 1, 2)
Explanation: 3.6 [1] Functions can be arguments to functions
End of explanation
print(type(say_hello))
a = say_hello
a()
id(say_hello)
id(a)
Explanation: 3.7 [1] Of course, functions are objects!
Functions are objects which can, for example, be "copied" to other objects:
End of explanation
def factorial(x):
    if x == 0:
        return 1
    else:
        return x * factorial(x-1)
print(factorial(3))
import time
now = time.time()
print(factorial(500))
print ("Time =", time.time() - now, "seconds")
Explanation: 4 [0] Recursion
Call yourself!
End of explanation
def outter():
    def inner():
        print('Hello world')
    inner()
    
outter()
Explanation: 5 [1] Nesting functions
End of explanation
def divide(numerator, denominator):
    return numerator/denominator
def safe_division(function):
    def wrapper(numerator, denominator):
        if denominator != 0:
            return function(numerator, denominator)
    return wrapper
# Function "decoration".
divide = safe_division(divide)
print(divide(1,2))
print(divide(1,0))
None == 0
Explanation: 6 [1] Decorating functions
Extending the behavior of functions that we don't want (or cannot) to modify:
End of explanation
# This function is identical to the previous one
def safe_division(function):
    def wrapper(numerator, denominator):
        if denominator != 0:
            return function(numerator, denominator)
    return wrapper
@safe_division
def divide(numerator, denominator):
    return numerator/denominator
print(divide(1,2))
print(divide(1,0))
# http://www.bytemining.com/2010/02/be-careful-searching-python-dictionaries/
import time
def print_timing(func):
    def wrapper(*arg):
        t1 = time.clock()
        res = func(*arg)
        t2 = time.clock()
        print('%0.3fms' % ((t2-t1)*1000.0))
        return res
    return wrapper
 
@print_timing
def my_function():
     #stuff
     return
my_function()
Explanation: The same example using a decorator:
End of explanation
# Standard function:
def power(x,y):
    return x**y
power(2,3)
# Lambda function:
power = lambda x,y: x**y
power(2,3)
Explanation: 7 [1] Lambda functions
$\lambda$-funcions are "anonymous" functions:
End of explanation
(lambda x,y: x**y)(2,3)
Explanation: Lambda functions are useful because they can be defined inline and we don't need give a name to use them:
End of explanation
help(filter)
Explanation: 7.1 Filtering data with $\lambda$-functions
... and filter() ...
End of explanation
for i in filter(lambda x: x < 0, range(-3,3)):
    print (i)
tuple(filter(lambda x: x%2, range(5*2)))
Explanation: In other words ..., filter() creates a sequences of elements for which a function returns true.
End of explanation
help(map)
Explanation: 7.2 Mapping data with $\lambda$-functions
... and map() ...
End of explanation
list(map(lambda x: x%2, range(10)))
Explanation: In other words ..., map() applies a $\lambda$-function to all the items in an input sequence.
End of explanation
from functools import reduce
help(reduce)
Explanation: 7.3 Reducing data with $\lambda$-functions
End of explanation
n=10; list(range(1, n+1))
def factorial(n):
    return reduce(lambda x,y: x*y, list(range(1, n+1)))
#                 ---------------  ------------------
#                    function           sequence
print(factorial(3))
Explanation: In other words ..., reduce() process iteratively a sequence of items. One of them is item of the sequence and the other, the previus output of the reduce().
End of explanation
import math
print(math.factorial(3))
now = time.time()
factorial(100000)
print ("Time =", time.time() - now)
now = time.time()
math.factorial(100000)
print ("Time =", time.time() - now)
Explanation: Only to see the performance:
End of explanation
# https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
import math
N = 20
primes = [True]*N
print('[', end='')
for j in range(2, N):
    if primes[j]:
        print(j, end=', ')
print(']')
for i in range(2, int(math.sqrt(N))):
    if primes[i]:
        for j in [i**2+x*i for x in range(N) if i**2+x*i<N]:
            primes[j] = False
    print('[', end='')
    for j in range(2, N):
        if primes[j]:
            print(j, end=', ')
    print(']', i)
    
# Be aware of this code does not produce a list!
Explanation: Another interesting example:
Now, lets compute prime numbers. First using a classical approach:
End of explanation
# A different implementation of the Sieve of Eratosthenes
# (http://stackoverflow.com/questions/27990094/finding-primes-with-modulo-in-python)
primes = list(range(2, N))
print(primes)
for i in range(2, int(math.sqrt(N))):
    primes = list(filter(lambda x: x == i or x % i, primes))
    print(primes, i)
# This code produces a list
Explanation: Now using $\lambda$-functions:
End of explanation
id(1) # The `id()` funtion returns the address of a object
a = 1
id(a) # "a" is the same object than "1"
Explanation: 8 [0] Namespaces and scopes
8.1 [0] Names
Python objects are referenced by names:
End of explanation
id(2)
Explanation: Different objects have different id's:
End of explanation
def func():
    a = 1
    print(a)
func()
print(a)
Explanation: A namespace is a collection of names. All packages, modules, classes and functions define their own namespace (basically, the variables -- or names -- locally defined). The scope of a name is the region of code where that name can be referenced without using any prefix.
8.2 [0] Functions create their own namespace
(Please, restart this kernel) Do you think that the second print(a) of in the following code should work?
End of explanation
a = 1
def func():
    print(a) # This is the external 'a'
func()
Explanation: [0] 8.3 Names of outter scopes are accessible in inner ones
End of explanation
a = 1
def func():
    a = 2
    print(a) # This is the internal 'a'
func()
print(a)
Explanation: 8.4 [0] When there is a coincidence, local scopes create their own names
End of explanation
a = 1
def func():
    global a
    a = 2 # This is the external 'a'
func()
print(a)
Explanation: 8.5 [1] To avoid creating a new name when there is a coincidence, use global
However, this is DISCOURAGED. It is confusing and isn’t thread-safe (among other things).
End of explanation
a = 1
def func(a):
    a = 2
    return a
a = func(a)
print(a)
Explanation: It's better to do:
End of explanation
<END_TASK>