Tutorial A8 – Solutions

1 Write a functions that takes two numbers as input and returns the sum.

[1]:
def calc_sum(a, b):
    """Calculate the sum of two values"""

    return a + b

print(calc_sum(1, 2))
3

2 Write a function that computes the cumulative sum of values in a sequence and returns the result as a list. Demonstrate that it works. Hint: You can use a list to pass a sequence to a function.

[2]:
def cumsum(seq):
    """Compute cumulative sum

    Args:
       seq: Sequence of numbers

    Returns:
       Cumulative sum as list
    """

    result = []
    for i, _ in enumerate(seq, 1):
        result.append(
            sum(seq[:i])
            )
    return result

print(cumsum(range(10)))
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

3 Write a function factorial(n) that returns the factorial of an integer \(n\). What happens if you pass a float?

[3]:
def factorial(n):
    """Compute the factorial of an integer"""

    product = 1
    while n > 1:
        product *= n
        n -= 1

    return product
[4]:
# Function returns the correct answer
for i in range(5):
    print(factorial(i))
1
1
2
6
24
[5]:
# Function returns nonsense if you pass a float
print(factorial(2.4))
3.36
[6]:
# How to ensure n is an integer?
def factorial_save(n):
    """Compute the factorial of an integer"""

    assert isinstance(n, int)
    # Raise an error if n is not an int

    product = 1
    while n > 1:
        product *= n
        n -= 1

    return product
[7]:
# Function raises an error if n is not an int
print(factorial_save(2.4))
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-7-c24e4c730d4c> in <module>
      1 # Function raises an error if n is not an int
----> 2 print(factorial_save(2.4))

<ipython-input-6-64b99c35c800> in factorial_save(n)
      3     """Compute the factorial of an integer"""
      4
----> 5     assert isinstance(n, int)
      6     # Raise an error if n is not an int
      7

AssertionError:

Advanced 1 Write a function exp(x) that takes a float x as an argument and returns \(\mathrm{e}^x\) approximated as the Taylor series up to order \(n\) around the expansion point \(a = 0\). Make the order \(n\) an optional argument that defaults to \(n = 5\). You can call factorial(n) from within exp(x) to calculate the factorials.

\[T(x; a) = \sum_{n = 0}^\infty \frac{f^{(n)}(a)}{n!} (x - a)^n\]

For \(f(x) = \mathrm{e}^x\), \(a = 0\):

\[T(x; 0) = \sum_{n = 0}^\infty \frac{x^{n}}{n!}\]
[8]:
def exp(x, n=5):
    """Calculate e**x approximated by a Taylor expansion around 0

    Args:
        x (float): Input value
        n (int): Expansion order

    Returns:
        Result (float)
    """

    assert isinstance(n, int)

    result = 1  # Zero order approximation
    for i in range(1, n+1):
        # Add term
        result += x ** i / factorial(i)

    return result
[9]:
# Generate 21 values in the open interval (-1, 1)
x = [x*0.1 for x in range(-10, 11)]

# Call our function (default n = 5)
for x_ in x:
    print(exp(x_))
0.3666666666666667
0.40591675000000005
0.44900266666666666
0.49643691666666656
0.5487519999999999
0.6065104166666666
0.6703146666666665
0.74081725
0.8187306666666667
0.9048374166666667
1.0
1.1051709166666666
1.2214026666666666
1.34985775
1.4918186666666666
1.6486979166666667
1.8220480000000003
2.013571416666667
2.225130666666667
2.45875825
2.7166666666666663
[10]:
# Call our function (lower order)
for x_ in x:
    print(exp(x_, n=2))
0.5
0.505
0.52
0.5449999999999999
0.58
0.625
0.6799999999999999
0.745
0.8200000000000001
0.905
1.0
1.105
1.22
1.345
1.48
1.625
1.7800000000000002
1.9450000000000003
2.12
2.3049999999999997
2.5

Advanced 2 What is wrong with these two functions:

[11]:
def broken():
    print(x)
    x = "NEW"

    return(x)

x = "STRING"
[12]:
def broken_counter():
    counter += 1

counter = 1

Advanced 3 (Warning: the solution to this problem touches a concept we have not discussed in the lesson. This is a really advanced exercise.) Ever solved a “Jumble”-puzzle? Write a function that expects a string and returns every possible re-combination of the letters to help you find the terms hidden behind “rta”, “atob” and “rbani”. You can test your function with the built-in function itertools.permutations from the itertools module. How many possible combinations does this return? Does the solver help you with this one: “ibaptlienxraoc”? Hint: Functions can call themselves. Try to pick letters from the string one by one to find a possible re-combination.

[13]:
# Solution using recursion
def recombine(pool, combination=''):
    if len(pool) == 0:
        # Pool exhausted, combination found
        combinations.append(combination)
    else:
        for i in range(len(pool)):
            # Take out one element and append it to combination
            recombine(pool[0:i] + pool[i + 1:], combination + pool[i])
[14]:
combinations = []
recombine("rta")
print(*combinations, sep="    ")
rta    rat    tra    tar    art    atr
[15]:
combinations = []
recombine("atob")
print(*combinations, sep="    ")
atob    atbo    aotb    aobt    abto    abot    taob    tabo    toab    toba    tbao    tboa    oatb    oabt    otab    otba    obat    obta    bato    baot    btao    btoa    boat    bota
[16]:
combinations = []
recombine("rbani")
print(*combinations, sep="    ")
rbani    rbain    rbnai    rbnia    rbian    rbina    rabni    rabin    ranbi    ranib    raibn    rainb    rnbai    rnbia    rnabi    rnaib    rniba    rniab    riban    ribna    riabn    rianb    rinba    rinab    brani    brain    brnai    brnia    brian    brina    barni    barin    banri    banir    bairn    bainr    bnrai    bnria    bnari    bnair    bnira    bniar    biran    birna    biarn    bianr    binra    binar    arbni    arbin    arnbi    arnib    aribn    arinb    abrni    abrin    abnri    abnir    abirn    abinr    anrbi    anrib    anbri    anbir    anirb    anibr    airbn    airnb    aibrn    aibnr    ainrb    ainbr    nrbai    nrbia    nrabi    nraib    nriba    nriab    nbrai    nbria    nbari    nbair    nbira    nbiar    narbi    narib    nabri    nabir    nairb    naibr    nirba    nirab    nibra    nibar    niarb    niabr    irban    irbna    irabn    iranb    irnba    irnab    ibran    ibrna    ibarn    ibanr    ibnra    ibnar    iarbn    iarnb    iabrn    iabnr    ianrb    ianbr    inrba    inrab    inbra    inbar    inarb    inabr
[17]:
# Test with built-in function
import itertools

print(*["".join(x) for x in itertools.permutations("rta")], sep="    ")
rta    rat    tra    tar    art    atr
[18]:
# The number of possible recombinations growths fast (n!)
import math

math.factorial(len("ibaptlienxraoc"))
[18]:
87178291200