Idiomatic Python

Day 25: Exercise Solutions

Python Guru with a screen instead of a face, typing on a computer keyboard with a purple-ish background to match the day 25 image.

Here are our solutions for the day 25 exercises in the 30 Days of Python series. Make sure you try the exercises yourself before checking out the solutions!

1) Write a function that prompts the user for their name and then greets them. If after processing the string you're left with an empty string, the function should replace the empty string with "World" in the output.

Let's start by defining our function.

def greeter():
    pass

The first thing we need to put in the function body is our input call to grab the user's name. I'm going to process the string all on the same line.

def greeter():
    name = input("Please enter your name: ").strip().title()

The only thing left to do is print the greeting. We can use or directly in our f-string to change the output depending on whether or not name ended up as an empty string.

def greeter():
    name = input("Please enter your name: ").strip().title()
    print(f"Hello, {name or 'World'}!")

Be careful not to use the same type as quotes inside the curly braces as you used for the outer string. If you do, you'll terminate your string early and it will lead to an error.

2) Write a function to determine whether or not a string contains exclusively ASCII letters.

Once again, let's start by defining the skeleton of our function.

def is_ascii_letters(test_string):
    pass

For this exercise, I think our best approach is going to be importing the string module and making use of the string constants defined inside. There is one called ascii_letters which includes all uppercase and lowercase ASCII letters, which is exactly what we want.

We can then loop over the test_string and check if a given character is in ascii_letters. If we find a character which isn't in this string constant, we can return False right away.

from string import ascii_letters

def is_ascii_letters(test_string):
    for character in test_string:
        if character not in ascii_letters:
            return False
    else:
        return True

Note that we're using an else clause with the for loop here, not with the conditional statement inside the loop. We talked about this back in day 8.

This is a perfectly fine solution, but we don't need to implement such a verbose loop structure like this. Instead, we can use the all function in combination with a generator expression.

from string import ascii_letters

def is_ascii_letters(test_string):
    return all(character in ascii_letters for character in test_string)

This is functionally identical, but written with some of the more advanced tools we've learnt in the last week of the course.

3) Use the sample function in the random module to create three lists, each containing fifteen numbers from 1 to 100 (inclusive). Sort each of these lists into descending order (largest first), and then truncate each list so that only 5 items remain in each.

Let's start by importing sample and generating our initial lists of numbers. The sample function expected a population and as well as a value representing how many numbers it should select from the population. The population needs to be a sequence, so we can provide a range.

import random

numbers = [random.sample(range(1, 101), 15) for _ in range(3)]

This is a fairly dense line of code, but I think it's still readable enough. If you like, you could extract the population to a variable so we don't have ranges all over the place in our comprehension.

import random

population = range(1, 101)
numbers = [random.sample(population, 15) for _ in range(3)]

Our next step is to sort the lists we get back from sample. We can use the sorted function as part of the comprehension to do this all in one step.

Since we want the numbers in descending order, we need to provide a value for the reverse parameter, passing in True.

import random

population = range(1, 101)
numbers = [sorted(random.sample(population, 15), reverse=True) for _ in range(3)]

However, I think this is getting a bit too much for a comprehension, and we're about to iterate over this collection with a for loop anyway.

Instead I would use the sort method like this:

import random

population = range(1, 101)
numbers = [random.sample(population, 15) for _ in range(3)]

for number_set in numbers:
    number_set.sort(reverse=True)

Once again we need to remember to pass in a value for reverse so that the list is sorted in descending order.

Now we can use del and the slice syntax to truncate the lists as part of the same loop.

import random

population = range(1, 101)
numbers = [random.sample(population, 15) for _ in range(3)]

for number_set in numbers:
    number_set.sort(reverse=True)
    del number_set[5:]

If we now print numbers, we should have three lists that have the highest five numbers in each.