back

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 a PEOPLE constant 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 abs to key in sorted?

    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 lambda that 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 sum as key, no?

    print(sum([]))
    
    0
    

    sum returns 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]]
    
mail@jonahv.com