python workout: exercise 13
printing tuple records
problem
For example, assume we’re in charge of an international summit in London. We know how many hours it’ll take each of several world leaders to arrive:
PEOPLE = [('Donald', 'Trump', 7.85), ('Vladimir', 'Putin', 3.626), ('Jinping', 'Xi', 10.603)]The planner for this summit needs to have a list of the world leaders who are coming, along with the time it’ll take for them to arrive. However, this travel planner doesn’t need the degree of precision that the computer has provided; it’s enough for us to have two digits after the decimal point.
For this exercise, write a Python function,
format_sort_records, that takes the PEOPLE list and returns a formatted string that looks like the following:Trump Donald 7.85 Putin Vladimir 3.63 Xi Jinping 10.60Notice that the last name is printed before the first name (taking into account that Chinese names are generally shown that way), followed by a decimal-aligned indication of how long it’ll take for each leader to arrive in London. Each name should be printed in a 10-character field, and the time should be printed in a 5-character field, with one space character of padding between each of the columns. Travel time should display only two digits after the decimal point, which means that even though the input for Xi Jinping’s flight is 10.603 hours, the value displayed should be
10.60.
attempts
This should be straightforward.
I just need to remember how to format x-character fields; I remember doing something similar in David Beazley’s practical-python.
But a quick look at the docs gives us what we need.
So it should look something like:
PEOPLE = [('Donald', 'Trump', 7.85),
('Vladimir', 'Putin', 3.626),
('Jinping', 'Xi', 10.603)]
def format_sort_records(records: list[tuple]) -> str:
formatted_string = []
for first_name, last_name, eta in records:
formatted_string.append(f"{last_name:<10} {first_name:<10} {eta:<5.2f}")
return "\n".join(formatted_string)
print(format_sort_records(PEOPLE))
Trump Donald 7.85
Putin Vladimir 3.63
Xi Jinping 10.60
We use the list approach to string construction because of the immutability of strings in Python.
And looking back at it now, we can just do this in a single line:
PEOPLE = [('Donald', 'Trump', 7.85),
('Vladimir', 'Putin', 3.626),
('Jinping', 'Xi', 10.603)]
def format_sort_records(records: list[tuple]) -> str:
return "\n".join(
f"{last_name:<10} {first_name:<10} {eta:<5.2f}"
for first_name, last_name, eta in records
)
print(format_sort_records(PEOPLE))
Trump Donald 7.85
Putin Vladimir 3.63
Xi Jinping 10.60
I think I might even prefer this…
correction
Reading the start of the book’s solution, it seems we had to sort the names alphabetically according to last name and first name.
I suspected as much given the inclusion of “sort” in the name of function we had to implement. But it wasn’t clear to me.
Taking that into account, we can update/correct our final one line solution by
leveraging sorted and operator.itemgetter as we did before:
from operator import itemgetter
PEOPLE = [('Donald', 'Trump', 7.85),
('Vladimir', 'Putin', 3.626),
('Jinping', 'Xi', 10.603)]
def format_sort_records(records: list[tuple]) -> str:
return "\n".join(
f"{last_name:<10} {first_name:<10} {eta:<5.2f}"
for first_name, last_name, eta in sorted(records, key=itemgetter(1,0))
)
print(format_sort_records(PEOPLE))
Putin Vladimir 3.63
Trump Donald 7.85
Xi Jinping 10.60
But what’s confusing is that this doesn’t match the example in the book’s problem statement:
Trump Donald 7.85
Putin Vladimir 3.63
Xi Jinping 10.60
And at the same time, the book’s formatted string isn’t sorted by last name or first name?
solution
The book’s implementation:
import operator
PEOPLE = [('Donald', 'Trump', 7.85),
('Vladimir', 'Putin', 3.626),
('Jinping', 'Xi', 10.603)]
def format_sort_records(list_of_tuples):
output = []
template = '{1:10} {0:10} {2:5.2f}'
for person in sorted(list_of_tuples,
key=operator.itemgetter(1, 0)):
output.append(template.format(*person))
return output
print('\n'.join(format_sort_records(PEOPLE)))
Putin Vladimir 3.63
Trump Donald 7.85
Xi Jinping 10.60
I like the use of a template string and str.format instead of f-strings. I think
I might even prefer it.
But I don’t understand why it returns a list of strings instead of a singular formatted string as stated in the problem statement?
I also didn’t notice that the times were meant to be aligned right. But that’s a small fix.
beyond the exercise
namedtuples
-
problem
Reimplement this exercise using
namedtupleobjects (http://mng.bz/gyWl), defined in thecollectionsmodule. Many people like to use named tuples because they give the right balance between readability and efficiency.
-
attempts
Looking at the docs, we’d start by creating a
Personnamedtuplebefore instantiating our examplePEOPLEdata with it. It should then just be a question of changing the arguments toitemgetter.And all this should work well building on top of the book’s implementation:
from operator import itemgetter from collections import namedtuple Person = namedtuple('Person', ['first_name', 'last_name', 'eta']) PEOPLE = [Person('Donald', 'Trump', 7.85), Person('Vladimir', 'Putin', 3.626), Person('Jinping', 'Xi', 10.603)] def format_sort_people(people: list[Person]) -> str: template = '{1:10} {0:10} {2:5.2f}' return '\n'.join( template.format(*person) for person in sorted(people, key=itemgetter(1,0)) ) print(format_sort_people(PEOPLE))Putin Vladimir 3.63 Trump Donald 7.85 Xi Jinping 10.60The only trouble is that we don’t make use of the named fields with
itemgetter. Unless I’m missing something?We could replace it with a lambda to give:
from collections import namedtuple Person = namedtuple('Person', ['first_name', 'last_name', 'eta']) PEOPLE = [Person('Donald', 'Trump', 7.85), Person('Vladimir', 'Putin', 3.626), Person('Jinping', 'Xi', 10.603)] def format_sort_people(people: list[Person]) -> str: template = '{1:10} {0:10} {2:5.2f}' return '\n'.join( template.format(*person) for person in sorted(people, key=lambda p: (p.last_name, p.first_name)) ) print(format_sort_people(PEOPLE))Putin Vladimir 3.63 Trump Donald 7.85 Xi Jinping 10.60Actually,
operator.attrgettermight do the job:from operator import attrgetter from collections import namedtuple Person = namedtuple('Person', ['first_name', 'last_name', 'eta']) PEOPLE = [Person('Donald', 'Trump', 7.85), Person('Vladimir', 'Putin', 3.626), Person('Jinping', 'Xi', 10.603)] def format_sort_people(people: list[Person]) -> str: template = '{1:10} {0:10} {2:5.2f}' return '\n'.join( template.format(*person) for person in sorted(people, key=attrgetter('last_name', 'first_name')) ) print(format_sort_people(PEOPLE))Putin Vladimir 3.63 Trump Donald 7.85 Xi Jinping 10.60And it does!
Very nice!
oscar nominees
-
problem
Define a list of tuples, in which each tuple contains the name, length (in min- utes), and director of the movies nominated for best picture Oscar awards last year. Ask the user whether they want to sort the list by title, length, or director’s name, and then present the list sorted by the user’s choice of axis.
-
attempts
Let’s reuse
namedtuplefor this. It’s a nice, organised way of representing our data.Which, after a quick search, looks like this for the 2025 Best Picture Oscar nominees:
from collections import namedtuple Film = namedtuple('Film', ['name', 'length', 'director']) NOMINEES = [ Film("I'm still here", 138, 'Walter Salles'), Film('Conclave', 120, 'Edward Berger'), Film('Dune: Part Two', 167, 'Denis Villeneuve'), Film('Anora', 139, 'Sean Baker'), Film('The Substance', 141, 'Coralie Fargeat'), Film('Wicked', 162, 'Jon M. Chu'), Film('The Brutalist', 215, 'Brady Corbet'), Film('A Complete Unknown', 140, 'James Mangold'), Film('Nickel Boys', 140, 'RaMell Ross'), Film('Emilia Perez', 132, 'Jacques Audiard') ]To control the sorted order, we’ll just take an additional argument. It will be the attribute name of the named tuple the user wants to sort by. And for the sake of simplicity, we want perform any validation/checks on whether the attribute name is correct. We’ll just let things fail:
from collections import namedtuple from operator import attrgetter Film = namedtuple('Film', ['title', 'duration', 'director']) NOMINEES = [ Film("I'm still here", 138, 'Walter Salles'), Film('Conclave', 120, 'Edward Berger'), Film('Dune: Part Two', 167, 'Denis Villeneuve'), Film('Anora', 139, 'Sean Baker'), Film('The Substance', 141, 'Coralie Fargeat'), Film('Wicked', 162, 'Jon M. Chu'), Film('The Brutalist', 215, 'Brady Corbet'), Film('A Complete Unknown', 140, 'James Mangold'), Film('Nickel Boys', 140, 'RaMell Ross'), Film('Emilia Perez', 132, 'Jacques Audiard') ] def format_sort_films(films: list[Film], sort_by: str) -> str: template = '{0:20} {2:20} {1:5}' return '\n'.join( template.format(*film) for film in sorted(films, key=attrgetter(sort_by)) ) print(format_sort_films(NOMINEES, 'title')) print() print(format_sort_films(NOMINEES, 'duration')) print() print(format_sort_films(NOMINEES, 'director'))A Complete Unknown James Mangold 140 Anora Sean Baker 139 Conclave Edward Berger 120 Dune: Part Two Denis Villeneuve 167 Emilia Perez Jacques Audiard 132 I'm still here Walter Salles 138 Nickel Boys RaMell Ross 140 The Brutalist Brady Corbet 215 The Substance Coralie Fargeat 141 Wicked Jon M. Chu 162 Conclave Edward Berger 120 Emilia Perez Jacques Audiard 132 I'm still here Walter Salles 138 Anora Sean Baker 139 A Complete Unknown James Mangold 140 Nickel Boys RaMell Ross 140 The Substance Coralie Fargeat 141 Wicked Jon M. Chu 162 Dune: Part Two Denis Villeneuve 167 The Brutalist Brady Corbet 215 The Brutalist Brady Corbet 215 The Substance Coralie Fargeat 141 Dune: Part Two Denis Villeneuve 167 Conclave Edward Berger 120 Emilia Perez Jacques Audiard 132 A Complete Unknown James Mangold 140 Wicked Jon M. Chu 162 Nickel Boys RaMell Ross 140 Anora Sean Baker 139 I'm still here Walter Salles 138
user enabled sort
-
problem
Extend this exercise by allowing the user to sort by two or three of these fields, not just one of them. The user can specify the fields by entering them separated by commas; you can use
str.splitto turn them into a list.
-
attempts
We’ve been sorting by multiple fields from the start.
This exercise extension just requires us to use
inputand pass the results ofstr.split()intoitemgetterorattrgetterdepending on whether we’re using regular tuples ornamedtuple.If we build off the first extension exercise we should get something like:
from operator import attrgetter from collections import namedtuple Person = namedtuple('Person', ['first_name', 'last_name', 'eta']) PEOPLE = [Person('Donald', 'Trump', 7.85), Person('Vladimir', 'Putin', 3.626), Person('Jinping', 'Xi', 10.603)] def format_sort_people(people: list[Person]) -> str: template = '{1:10} {0:10} {2:5.2f}' print(f'Available fields to sort by: {Person._fields}') sortby = input(f"Sort by field(s): ").split(',') return '\n'.join( template.format(*person) for person in sorted(people, key=attrgetter(*sortby)) ) print(format_sort_people(PEOPLE))Available fields to sort by: ('first_name', 'last_name', 'eta') Sort by field(s): eta,last_name Putin Vladimir 3.63 Trump Donald 7.85 Xi Jinping 10.60It’s not perfect. But it does the job.