python workout: exercise 11
alphabetising names
problem
Let’s assume you have phone book data in a list of dicts, as follows:
PEOPLE = [{'first':'Reuven', 'last':'Lerner', 'email':'reuven@lerner.co.il'}, {'first':'Donald', 'last':'Trump', 'email':'president@whitehouse.gov'}, {'first':'Vladimir', 'last':'Putin', 'email':'president@kremvax.ru'}]First of all, if these are the only people in your phone book, then you should rethink whether Python programming is truly the best use of your time and connections. Regardless, write a function,
alphabetize_names, that assumes the existence of aPEOPLEconstant defined as shown in the code. The function should return the list of dicts, but sorted by last name and then by first name.
attempts
The problem tells us where to look – sorted and operator.itemgetter.
I already know what’s possible with sorted and its key argument. But I know
nothing of operator.itemgetter.
Reading up on it, it’s quite a nice little function that returns a callable
that, given an argument, will fetch an item in that argument at the index
specified in the creation of the callable. And it does so by using the
argument’s underlying __getitem__() method.
We could actually use operator.itemgetter in conjuction with sorted for a clean
solution:
from operator import itemgetter
def alphabetise_names(names: list[dict[str,str]]) -> list[dict[str,str]]:
return sorted(names, key=itemgetter("last", "first"))
PEOPLE = [{'first':'Reuven', 'last':'Lerner', 'email':'reuven@lerner.co.il'},
{'first':'Donald', 'last':'Trump', 'email': 'president@whitehouse.gov'},
{'first':'Ivanka', 'last':'Trump', 'email': 'family@whitehouse.gov'},
{'first':'Barron', 'last':'Trump', 'email': 'family@whitehouse.gov'},
{'first':'Vladimir', 'last':'Putin', 'email':'president@kremvax.ru'}]
print(alphabetise_names(PEOPLE))
[{'first': 'Reuven', 'last': 'Lerner', 'email': 'reuven@lerner.co.il'}, {'first': 'Vladimir', 'last': 'Putin', 'email': 'president@kremvax.ru'}, {'first': 'Barron', 'last': 'Trump', 'email': 'family@whitehouse.gov'}, {'first': 'Donald', 'last': 'Trump', 'email': 'president@whitehouse.gov'}, {'first': 'Ivanka', 'last': 'Trump', 'email': 'family@whitehouse.gov'}]
This is a really elegant solution to me. And it all comes down to itemgetter’s
ability to take multiple arguments and return a tuple of retrieved items instead.
It’s also nice that sorted’s key argument can handle that kind of behaviour.
Overall, it beats passing a lambda expression to key in my book.
solution
The book’s implementation:
import operator
PEOPLE = [{'first': 'Reuven', 'last': 'Lerner',
'email': 'reuven@lerner.co.il'},
{'first': 'Donald', 'last': 'Trump',
'email': 'president@whitehouse.gov'},
{'first': 'Vladimir', 'last': 'Putin',
'email': 'president@kremvax.ru'}
]
def alphabetize_names(list_of_dicts):
return sorted(list_of_dicts, key=operator.itemgetter('last', 'first'))
print(alphabetize_names(PEOPLE))
[{'first': 'Reuven', 'last': 'Lerner', 'email': 'reuven@lerner.co.il'}, {'first': 'Vladimir', 'last': 'Putin', 'email': 'president@kremvax.ru'}, {'first': 'Donald', 'last': 'Trump', 'email': 'president@whitehouse.gov'}]
And it’s the exact same!
beyond the exercise
sorting by absolute value
-
problem
Given a sequence of positive and negative numbers, sort them by absolute value.
-
attempts
Surely this is just a question of passing
abstokeyinsorted?from numbers import Number def abssort(numbers: list[Number]) -> list[Number]: return sorted(numbers, key=abs) print(abssort([-1,-30,23,2,-4]))[-1, 2, -4, 23, -30]
sorting by vowels
-
problem
Given a list of strings, sort them according to how many vowels they contain.
-
attempts
Now we can juse use a
lambdathat counts the number of vowels in a string:def vowelsort(strings: list[str]) -> list[str]: return sorted(strings, key=lambda s: sum(1 for letter in s if letter in 'aeiou')) print(vowelsort(['absolutely', 'hello', 'goodbye', 'well', 'nun']))['well', 'nun', 'hello', 'goodbye', 'absolutely']The only question is whether there’s a more elegant solution that avoids the
lambda?
sorting by inner sum
-
problem
Given a list of lists, with each list containing zero or more numbers, sort by the sum of each inner list’s numbers.
-
attempts
Surely this is just a question of using
sumaskey, no?print(sum([]))0sumreturns 0 for empty lists. So this should work no problem:from numbers import Number def innersumsort(lists: list[list[Number]]) -> list[list[Number]]: return sorted(lists, key=sum) print(innersumsort([[1,2,3], [-20,3,9], [], [2,4,6,8,16]]))[[-20, 3, 9], [], [1, 2, 3], [2, 4, 6, 8, 16]]