{"id":"functions-as-arguments-objects","slug":"own-objects","title":"Sorting your own objects","text":"Lists often hold objects of your own classes. Python doesn't know how to compare two such objects, so `sorted` on its own stops with an error:\n\n```python\nclass Book:\n    def __init__(self, title, author, year):\n        self.title = title\n        self.author = author\n        self.year = year\n\n\nbooks = [Book(\"Dune\", \"Frank Herbert\", 1965), Book(\"Emma\", \"Jane Austen\", 1815)]\nprint(sorted(books))\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 9, in <module>\n    print(sorted(books))\n          ~~~~~~^^^^^^^\nTypeError: '<' not supported between instances of 'Book' and 'Book'\n```\n\nSorting compares the items with `<`, and a `Book` has no `<`. You could add one with the operator overloading of part 10, but a book has no single natural order anyway: by title, by author or by year all make sense. A key function lets you choose, every time you sort.\n\n### A key that reads an attribute\n\nThe key function gets one object and returns the attribute to sort by. A named function and a lambda work equally well:\n\n```python\nclass Book:\n    def __init__(self, title, author, year):\n        self.title = title\n        self.author = author\n        self.year = year\n\n    def __str__(self):\n        return f\"{self.title} ({self.author}, {self.year})\"\n\n\ndef by_year(book):\n    return book.year\n\n\nbooks = [\n    Book(\"Dune\", \"Frank Herbert\", 1965),\n    Book(\"Emma\", \"Jane Austen\", 1815),\n    Book(\"Beloved\", \"Toni Morrison\", 1987),\n]\n\nprint(\"By year:\")\nfor book in sorted(books, key=by_year):\n    print(book)\n\nprint(\"By title:\")\nfor book in sorted(books, key=lambda book: book.title):\n    print(book)\n```\n\n```output\nBy year:\nEmma (Jane Austen, 1815)\nDune (Frank Herbert, 1965)\nBeloved (Toni Morrison, 1987)\nBy title:\nBeloved (Toni Morrison, 1987)\nDune (Frank Herbert, 1965)\nEmma (Jane Austen, 1815)\n```\n\nA key can call a method too, such as `key=lambda shape: shape.area()`, when the value comes from a method instead of an attribute.\n\n### Best, worst and ties\n\n`min` and `max` with a key return the whole object, so you can use any of its attributes afterwards. And when two objects have the same value, a key can return a **tuple** to settle the tie: tuples are compared item by item, so the second item counts only when the first items are equal. To sort a number from highest to lowest but names from A to Z, make the number negative in the key:\n\n```python\nclass Player:\n    def __init__(self, name, points):\n        self.name = name\n        self.points = points\n\n\nplayers = [Player(\"Uma\", 12), Player(\"Leo\", 20), Player(\"Ann\", 12), Player(\"Kai\", 7)]\n\nprint(max(players, key=lambda player: player.points).name)\nprint(min(players, key=lambda player: player.points).name)\n\nfor player in sorted(players, key=lambda player: (-player.points, player.name)):\n    print(player.name, player.points)\n```\n\n```output\nLeo\nKai\nLeo 20\nAnn 12\nUma 12\nKai 7\n```\n\nUma and Ann both have 12 points, so their names decide: Ann comes first.\n","task":"Fill in the blank so that the movies are listed from the shortest to the longest. The program should print:\n\n```\nUp (96 min)\nAlien (117 min)\nTitanic (194 min)\n```\n","kind":"fill","seed":"class Movie:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title} ({self.minutes} min)\"\n\n\nmovies = [Movie(\"Titanic\", 194), Movie(\"Up\", 96), Movie(\"Alien\", 117)]\nfor movie in sorted(movies, key=lambda movie: [[movie.minutes]]):\n    print(movie)\n","solution":"class Movie:\n    def __init__(self, title, minutes):\n        self.title = title\n        self.minutes = minutes\n\n    def __str__(self):\n        return f\"{self.title} ({self.minutes} min)\"\n\n\nmovies = [Movie(\"Titanic\", 194), Movie(\"Up\", 96), Movie(\"Alien\", 117)]\nfor movie in sorted(movies, key=lambda movie: movie.minutes):\n    print(movie)\n","hints":["The key gets one `Movie` object and returns the value to sort it by, its length.","The length is the object's attribute `minutes`: `movie.minutes`."],"tests":[{"name":"Output","stdin":"","expect":"Up (96 min)\nAlien (117 min)\nTitanic (194 min)","hidden":false}],"require":[],"forbid":[],"more":[{"task":"The class `Runner` holds a runner's name and finishing time in seconds. Write a function `fastest(runners)` that returns the **name** of the runner with the smallest time, as a string. For example,\n\n```python\nrunners = [Runner(\"Ines\", 312.5), Runner(\"Tom\", 298.1), Runner(\"Sam\", 305.0)]\nprint(fastest(runners))\n```\n\nshould print:\n\n```\nTom\n```\n","kind":"code","seed":"class Runner:\n    def __init__(self, name, seconds):\n        self.name = name\n        self.seconds = seconds\n\n\ndef fastest(runners):\n    pass\n\n\nif __name__ == \"__main__\":\n    runners = [Runner(\"Ines\", 312.5), Runner(\"Tom\", 298.1), Runner(\"Sam\", 305.0)]\n    print(fastest(runners))\n","solution":"class Runner:\n    def __init__(self, name, seconds):\n        self.name = name\n        self.seconds = seconds\n\n\ndef fastest(runners):\n    winner = min(runners, key=lambda runner: runner.seconds)\n    return winner.name\n\n\nif __name__ == \"__main__\":\n    runners = [Runner(\"Ines\", 312.5), Runner(\"Tom\", 298.1), Runner(\"Sam\", 305.0)]\n    print(fastest(runners))\n","hints":["`min` with a key finds the runner whose time is smallest, and returns the `Runner` object.","The key returns a runner's time: `key=lambda runner: runner.seconds`.","The function returns the name, so take `.name` of the object `min` gives you."],"tests":[{"name":"Test 1","stdin":"","call":"runners = [Runner(\"Ines\", 312.5), Runner(\"Tom\", 298.1), Runner(\"Sam\", 305.0)]\nprint(fastest(runners))\n","expect":"Tom","hidden":false},{"name":"fastest([Runner(\"Ana\", 251.0), Runner(\"Bo\", 260.4)])","stdin":"","call":"fastest([Runner(\"Ana\", 251.0), Runner(\"Bo\", 260.4)])\n","expect":"'Ana'","hidden":true,"returns":true},{"name":"fastest([Runner(\"Cy\", 400.0), Runner(\"Di\", 399.9), Runner(\"Ed\", 401.2)])","stdin":"","call":"fastest([Runner(\"Cy\", 400.0), Runner(\"Di\", 399.9), Runner(\"Ed\", 401.2)])\n","expect":"'Di'","hidden":true,"returns":true}],"require":[],"forbid":[]},{"task":"Write a function `leaderboard(players)` that returns a new list of the `Player` objects sorted by points, the most points first. Players with equal points are listed in alphabetical order by name. The class has a `__repr__`, so a list of players prints readably. For example,\n\n```python\nplayers = [Player(\"Zed\", 21), Player(\"Lia\", 30), Player(\"Abe\", 21), Player(\"Kim\", 5)]\nprint(leaderboard(players))\n```\n\nshould print:\n\n```\n[Lia (30), Abe (21), Zed (21), Kim (5)]\n```\n","kind":"code","seed":"class Player:\n    def __init__(self, name, points):\n        self.name = name\n        self.points = points\n\n    def __repr__(self):\n        return f\"{self.name} ({self.points})\"\n\n\ndef leaderboard(players):\n    pass\n\n\nif __name__ == \"__main__\":\n    players = [Player(\"Zed\", 21), Player(\"Lia\", 30), Player(\"Abe\", 21), Player(\"Kim\", 5)]\n    print(leaderboard(players))\n","solution":"class Player:\n    def __init__(self, name, points):\n        self.name = name\n        self.points = points\n\n    def __repr__(self):\n        return f\"{self.name} ({self.points})\"\n\n\ndef leaderboard(players):\n    return sorted(players, key=lambda player: (-player.points, player.name))\n\n\nif __name__ == \"__main__\":\n    players = [Player(\"Zed\", 21), Player(\"Lia\", 30), Player(\"Abe\", 21), Player(\"Kim\", 5)]\n    print(leaderboard(players))\n","hints":["The order depends on two things, so let the key return a tuple of two values.","Points go from highest to lowest and names from A to Z. Making the points negative lets one ordinary sort do both: `(-player.points, player.name)`.","Use `sorted`, not `sort`, so the caller's list keeps its order."],"tests":[{"name":"Test 1","stdin":"","call":"players = [Player(\"Zed\", 21), Player(\"Lia\", 30), Player(\"Abe\", 21), Player(\"Kim\", 5)]\nprint(leaderboard(players))\n","expect":"[Lia (30), Abe (21), Zed (21), Kim (5)]","hidden":false},{"name":"Test 2","stdin":"","call":"players = [Player(\"Bo\", 3), Player(\"Al\", 3), Player(\"Cy\", 9)]\nprint(leaderboard(players))\nprint(players)\n","expect":"[Cy (9), Al (3), Bo (3)]\n[Bo (3), Al (3), Cy (9)]","hidden":true},{"name":"print(leaderboard([Player(\"Max\", 14), Player(\"Eva\", 14), Player(\"Ian\", 14)]))","stdin":"","call":"print(leaderboard([Player(\"Max\", 14), Player(\"Eva\", 14), Player(\"Ian\", 14)]))\n","expect":"[Eva (14), Ian (14), Max (14)]","hidden":true}],"require":[],"forbid":[]}]}