Textbook Finger Exercises

The purpose of this notebook is to collect all the finger exercises I did from Introduction to Computation and Programming Using Python, third edition, With Application to Computational Modeling and Understanding Data by John V. Guttag.

(Btw it is not complete, I didn’t do for example some very basic finger exercises toward the beginning of the book.)

Chapter 3: Some Simple Numerical Programs

3.1 Exhaustive Enumeration

Write a program that asks the user to enter an integer and prints two integers, root and pwr, such that 1 < pwr < 6 and root**pwr is equal to the integer entered by the user. If no such pair of integers exists, it should print a message to that effect.

x = int(input("Enter an integer: "))
answer_root, answer_pwr = None, None
if x == 0:
    answer_root, answer_pwr = 0, 2
else:
    if x < 0:
        for pwr in range(3, 6, 2):
            for root in range(abs(x) + 1):
                root *= -1
                if root**pwr == x:
                    answer_root, answer_pwr = root, pwr
                    break
    else:
        for pwr in range(2, 6):
            for root in range(abs(x) + 1):
                if root**pwr == x:
                    answer_root, answer_pwr = root, pwr
                    break
if answer_root is not None:
    print(f"For {x}, root is {answer_root} and power is {answer_pwr}")
else:
    print("No such pair exists.")
For -8, root is -2 and power is 3

Write a program that prints the sum of the prime numbers greater than 2 and less than 1000. Hint: you probably want to have a loop that is a primality test nested inside a loop that iterates over the odd integers between 3 and 999.

x = 0
for i in range(3, 1000, 2):
    is_prime = True
    for j in range(2, i):
        if i % j == 0:
            is_prime = False
            break
    if is_prime:
        x += i
print(x)
76125

3.4 Newton-Raphson

Add some code to the implementation of Newton-Raphson (in Figure 3-7) that keeps track of the number of iterations used to find the root. Use that code as part of a program that compares the efficiency of Newton-Raphson and bisection search. (You should discover that Newton-Raphson is far more efficient). Note: I also changed the code to find the cube root.

k = float(input("Enter a number: "))
epsilon = 0.01
guess = k / 2
num_guesses = 1
while abs(guess**3 - k) >= epsilon:
    num_guesses += 1
    guess -= (guess**3 - k) / (3 * guess**2)
print(f"{num_guesses = }")
print("Cube root of", k, "is about", guess)
num_guesses = 8
Cube root of -27.0 is about -3.000000081210202

Chapter 4: Functions, Scoping, and Abstraction

Use find to implement a function satisfying the specifiaction

def find_last(s, sub):
    """s and sub are non-empty strings
    Returns the index of the last occurence of sub in s.
    Returns None if sub does not occur in s"""
    if s.find(sub) == -1:
        return None
    else:
        return len(s) - len(sub) - s[::-1].find(sub[::-1])

Chapter 5: Structured Types and Mutability

Using encoder and encrypt as models, implement the functions decoder and decrypt. Use them to decrypt the message

book = "In a village of La Mancha, the name of which I have no desire to call to mind, there lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing."
cipher_text = "22*13*33*137*59*11*23*11*1*57*6*13*1*2*6*57*2*6*1*22*13*33*137*59*11*23*11*1*57*6*173*7*11"
gen_decode_keys = lambda book, cipher_text: {
    s: book[int(s)] for s in set(cipher_text.split("*"))
}
decoder = lambda decode_keys, cipher_text: "".join(
    [decode_keys[s] for s in cipher_text.split("*")]
)
decrypt = lambda book, cipher_text: decoder(
    gen_decode_keys(book, cipher_text), cipher_text
)
decrypt(book, cipher_text)
'comprehension is incomprehensible'

Note: We could decipher the text without creating the decrypt function using the call decoder(gen_decode_keys(book, cipher_text), cipher_text). I mean, we have eveything we need to decrypt the message before we implement the decrypt function. I guess the function decrypt is created for convenience, so that to decipher a message, we have a function with a straightforward usage: One parameter is the book and one parameter is the cipher text and we don’t have to care about anything else. I think that is to honor the abstraction. We shouldn’t care about how the function works (i.e. that it uses decoder and gen_decode_keys functions) as long as it simply takes the book, the cipher text and produces the decoded the message.