From 85ca16b5b5330e268f9749fddd02f14fd0dfb894 Mon Sep 17 00:00:00 2001 From: Steve Oney Date: Thu, 27 Jul 2023 16:11:20 -0400 Subject: [PATCH 1/4] Add comparison operators to sorting discussion --- _sources/Classes/sorting_instances.rst | 56 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/_sources/Classes/sorting_instances.rst b/_sources/Classes/sorting_instances.rst index 43935e7c..4212e10d 100644 --- a/_sources/Classes/sorting_instances.rst +++ b/_sources/Classes/sorting_instances.rst @@ -18,7 +18,10 @@ Sorting Lists of Instances ========================== -You previously learned :ref:`how to sort lists `. Sorting lists of instances of a class is not fundamentally different from sorting lists of objects of any other type. There is a way to define a default sort order for instances, right in the class definition, but it requires defining a bunch of methods or one complicated method, so we won't bother with that. Instead, you should just provide a key function as a parameter to sorted (or sort). +You previously learned :ref:`how to sort lists `. Sorting lists of instances of a class is not fundamentally different from sorting lists of objects of any other type. There are two main ways to sort lists of instances: (1) by providing a ``key`` function as a parameter to ``sorted()`` (or ``.sort()``) or by (2) defining a "comparison operator" that determines how two instances should be compared (specifically, given two instances, which one should come first). We will describe both ways here. + +Approach 1: Sorting Lists of Instances with ``key`` +--------------------------------------------------- Previously, you have seen how to provide such a function when sorting lists of other kinds of objects. For example, given a list of strings, you can sort them in ascending order of their lengths by passing a key parameter. Note that if you refer to a function by name, you give the name of the function without parentheses after it, because you want the function object itself. The sorted function will take care of calling the function, passing the current item in the list. Thus, in the example below, we write ``key=len`` and not ``key=len()``. @@ -64,3 +67,54 @@ Sometimes you will find it convenient to define a method for the class that does for f in sorted(L, key=lambda x: x.sort_priority()): print(f.name) + +Approach 2: Defining Sort Orders with Comparison Operators +---------------------------------------------------------- + +Another approach to sorting lists of instances is to specify a "comparison operator" for the class---a method that takes two instances as arguments and "decides" which should come first. One advantage of this approach is that you can call ``sorted`` on a list of instances **without** specifying a value for ``key`` and it will sort in the order you defined. + +To do this, we can define a method named ``__lt__`` which stands for "less than". Note that this method starts and ends with two underscores. This signifies that it is a special method, just like ``__init__`` and ``__str__``. Our method, ``__lt__``, takes two instances as arguments: ``self`` and an argument for another instance. It returns ``True`` if the ``self`` instance should come before the other instance, and ``False`` otherwise. Normally, ``__lt__`` is called when we try to use the less than operator (``<``) on class instances; Python translates the expression ``a < b`` into ``a.__lt__(b)``. However, we can also use ``__lt__`` to decide which of two instances should come first in a sorted list. For example, if we wanted to sort instances of ``Fruit`` by prices names by default, we could define ``__lt__`` as follows: + +.. activecode:: sort_instances_4 + class Fruit(): + def __init__(self, name, price): + self.name = name + self.price = price + + def __lt__(self, other): # other is another instance of Fruit + return self.price < other.price + + cherry = Fruit("Cherry", 10) + apple = Fruit("Apple", 5) + blueberry = Fruit("Blueberry", 20) + L = [cherry, apple, blueberry] + + print("-----sorted using comparison operator (without key)-----") + for f in sorted(L): + print(f) + + print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; False + +When we call ``sorted(L)`` without specifying a value for the ``key`` parameter, it will sort the items in the list using the ``__lt__`` method we defined. ``sorted()`` will automatically call the ``__lt__`` method, passing in two instances from the list. Calling ``__lt__`` when ``self`` is ``Fruit("Cherry", 10)`` and ``other`` is ``Fruit("Apple", 5)`` returns ``False`` (because the ``price`` of the cherry is not less than the price of the apple) so this means ``Apple`` should come before ``Cherry`` in the sorted list. + +If we wanted to sort by names, we could define ``__lt__`` differently. *Note that when we call ``<`` on strings, it does an alphabetical comparison; ``"Apple" < "Cherry"`` is ``True``. We can take advantage of this in our ``__lt__`` method*: + +.. activecode:: sort_instances_5 + class Fruit(): + def __init__(self, name, price): + self.name = name + self.price = price + + def __lt__(self, other): # other is another instance of Fruit + return self.name < other.name # note we are comparing names + + cherry = Fruit("Cherry", 10) + apple = Fruit("Apple", 5) + blueberry = Fruit("Blueberry", 20) + L = [cherry, apple, blueberry] + + print("-----sorted using comparison operator (without key)-----") + for f in sorted(L): + print(f) + + print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; True \ No newline at end of file From 60a70df5ce9ee6b51cac1aa772fc708640039769 Mon Sep 17 00:00:00 2001 From: Steve Oney Date: Thu, 27 Jul 2023 16:32:12 -0400 Subject: [PATCH 2/4] Fix code samples and add clarifying explanations --- _sources/Classes/sorting_instances.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/_sources/Classes/sorting_instances.rst b/_sources/Classes/sorting_instances.rst index 4212e10d..1604f2f5 100644 --- a/_sources/Classes/sorting_instances.rst +++ b/_sources/Classes/sorting_instances.rst @@ -76,6 +76,7 @@ Another approach to sorting lists of instances is to specify a "comparison opera To do this, we can define a method named ``__lt__`` which stands for "less than". Note that this method starts and ends with two underscores. This signifies that it is a special method, just like ``__init__`` and ``__str__``. Our method, ``__lt__``, takes two instances as arguments: ``self`` and an argument for another instance. It returns ``True`` if the ``self`` instance should come before the other instance, and ``False`` otherwise. Normally, ``__lt__`` is called when we try to use the less than operator (``<``) on class instances; Python translates the expression ``a < b`` into ``a.__lt__(b)``. However, we can also use ``__lt__`` to decide which of two instances should come first in a sorted list. For example, if we wanted to sort instances of ``Fruit`` by prices names by default, we could define ``__lt__`` as follows: .. activecode:: sort_instances_4 + class Fruit(): def __init__(self, name, price): self.name = name @@ -91,7 +92,7 @@ To do this, we can define a method named ``__lt__`` which stands for "less than" print("-----sorted using comparison operator (without key)-----") for f in sorted(L): - print(f) + print(f.name) print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; False @@ -100,6 +101,7 @@ When we call ``sorted(L)`` without specifying a value for the ``key`` parameter, If we wanted to sort by names, we could define ``__lt__`` differently. *Note that when we call ``<`` on strings, it does an alphabetical comparison; ``"Apple" < "Cherry"`` is ``True``. We can take advantage of this in our ``__lt__`` method*: .. activecode:: sort_instances_5 + class Fruit(): def __init__(self, name, price): self.name = name @@ -115,6 +117,8 @@ If we wanted to sort by names, we could define ``__lt__`` differently. *Note tha print("-----sorted using comparison operator (without key)-----") for f in sorted(L): - print(f) + print(f.name) + + print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; True - print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; True \ No newline at end of file +Finally, note that if we pass in a ``key`` to ``sorted()`` (approach 1), it will use that instead of calling the ``__lt__`` method. \ No newline at end of file From 3d29785cff6b79ff88774009e5439b8a134b8443 Mon Sep 17 00:00:00 2001 From: Steve Oney Date: Mon, 31 Jul 2023 11:28:33 -0400 Subject: [PATCH 3/4] Typo fix --- _sources/Classes/sorting_instances.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_sources/Classes/sorting_instances.rst b/_sources/Classes/sorting_instances.rst index 1604f2f5..f2427ecd 100644 --- a/_sources/Classes/sorting_instances.rst +++ b/_sources/Classes/sorting_instances.rst @@ -73,7 +73,7 @@ Approach 2: Defining Sort Orders with Comparison Operators Another approach to sorting lists of instances is to specify a "comparison operator" for the class---a method that takes two instances as arguments and "decides" which should come first. One advantage of this approach is that you can call ``sorted`` on a list of instances **without** specifying a value for ``key`` and it will sort in the order you defined. -To do this, we can define a method named ``__lt__`` which stands for "less than". Note that this method starts and ends with two underscores. This signifies that it is a special method, just like ``__init__`` and ``__str__``. Our method, ``__lt__``, takes two instances as arguments: ``self`` and an argument for another instance. It returns ``True`` if the ``self`` instance should come before the other instance, and ``False`` otherwise. Normally, ``__lt__`` is called when we try to use the less than operator (``<``) on class instances; Python translates the expression ``a < b`` into ``a.__lt__(b)``. However, we can also use ``__lt__`` to decide which of two instances should come first in a sorted list. For example, if we wanted to sort instances of ``Fruit`` by prices names by default, we could define ``__lt__`` as follows: +To do this, we can define a method named ``__lt__`` which stands for "less than". Note that this method starts and ends with two underscores. This signifies that it is a special method, just like ``__init__`` and ``__str__``. Our method, ``__lt__``, takes two instances as arguments: ``self`` and an argument for another instance. It returns ``True`` if the ``self`` instance should come before the other instance, and ``False`` otherwise. Normally, ``__lt__`` is called when we try to use the less than operator (``<``) on class instances; Python translates the expression ``a < b`` into ``a.__lt__(b)``. However, we can also use ``__lt__`` to decide which of two instances should come first in a sorted list. For example, if we wanted to sort instances of ``Fruit`` by prices by default, we could define ``__lt__`` as follows: .. activecode:: sort_instances_4 From f13f3ab26b07b0578b5fabaa679e60b7350e813b Mon Sep 17 00:00:00 2001 From: Paul Resnick Date: Wed, 9 Aug 2023 15:44:19 -0400 Subject: [PATCH 4/4] exposition improvements for sorting_instances --- _sources/Classes/sorting_instances.rst | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/_sources/Classes/sorting_instances.rst b/_sources/Classes/sorting_instances.rst index f2427ecd..f5ff88c4 100644 --- a/_sources/Classes/sorting_instances.rst +++ b/_sources/Classes/sorting_instances.rst @@ -1,4 +1,4 @@ -.. Copyright (C) Paul Resnick. Permission is granted to copy, distribute +.. Copyright (C) Steve Oney. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with Invariant Sections being Forward, Prefaces, and @@ -18,22 +18,23 @@ Sorting Lists of Instances ========================== -You previously learned :ref:`how to sort lists `. Sorting lists of instances of a class is not fundamentally different from sorting lists of objects of any other type. There are two main ways to sort lists of instances: (1) by providing a ``key`` function as a parameter to ``sorted()`` (or ``.sort()``) or by (2) defining a "comparison operator" that determines how two instances should be compared (specifically, given two instances, which one should come first). We will describe both ways here. +You previously learned :ref:`how to sort lists `. Sorting lists of instances of a class is fundamentally the same as sorting lists of objects of any other type. There are two main ways to sort lists of instances: (1) by providing a ``key`` function as a parameter to ``sorted()`` (or ``.sort()``) or by (2) defining a "comparison operator" that determines how two instances should be compared (specifically, given two instances, which one should come first). We will describe both ways here. Approach 1: Sorting Lists of Instances with ``key`` --------------------------------------------------- -Previously, you have seen how to provide such a function when sorting lists of other kinds of objects. For example, given a list of strings, you can sort them in ascending order of their lengths by passing a key parameter. Note that if you refer to a function by name, you give the name of the function without parentheses after it, because you want the function object itself. The sorted function will take care of calling the function, passing the current item in the list. Thus, in the example below, we write ``key=len`` and not ``key=len()``. +Previously, you have seen how to provide such a function as input when sorting lists of other kinds of objects. For example, given a list of strings, you can sort them in ascending order of their lengths by passing ``len`` as the key parameter. Note that if you refer to a function by name, you give the name of the function without parentheses after it, because you want the function object itself. The sorted function will take care of calling the function, passing the current item in the list. Thus, in the example below, we write ``key=len`` and not ``key=len()``. .. activecode:: sort_instances_1 L = ["Cherry", "Apple", "Blueberry"] + print(sorted(L)) print(sorted(L, key=len)) #alternative form using lambda, if you find that easier to understand print(sorted(L, key= lambda x: len(x))) -When each of the items in a list is an instance of a class, you need to provide a function that takes one instance as an input, and returns a number. The instances will be sorted by their numbers. +When each of the items in a list is an instance of a class, the function you pass for the key parameter takes one instance as an input and returns a number. The instances will be sorted by their returned numbers. .. activecode:: sort_instances_2 @@ -85,8 +86,8 @@ To do this, we can define a method named ``__lt__`` which stands for "less than" def __lt__(self, other): # other is another instance of Fruit return self.price < other.price - cherry = Fruit("Cherry", 10) - apple = Fruit("Apple", 5) + apple = Fruit("Apple", 10) + cherry = Fruit("Cherry", 5) blueberry = Fruit("Blueberry", 20) L = [cherry, apple, blueberry] @@ -94,9 +95,9 @@ To do this, we can define a method named ``__lt__`` which stands for "less than" for f in sorted(L): print(f.name) - print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; False + print(f'apple < cherry: {apple < cherry}') # Equivalent to apple.__lt__(cherry) ; False -When we call ``sorted(L)`` without specifying a value for the ``key`` parameter, it will sort the items in the list using the ``__lt__`` method we defined. ``sorted()`` will automatically call the ``__lt__`` method, passing in two instances from the list. Calling ``__lt__`` when ``self`` is ``Fruit("Cherry", 10)`` and ``other`` is ``Fruit("Apple", 5)`` returns ``False`` (because the ``price`` of the cherry is not less than the price of the apple) so this means ``Apple`` should come before ``Cherry`` in the sorted list. +When we call ``sorted(L)`` without specifying a value for the ``key`` parameter, it will sort the items in the list using the ``__lt__`` method defined for the class of items. ``sorted()`` will automatically call the ``__lt__`` method, passing in two instances from the list. Calling ``__lt__`` when ``self`` is ``Fruit("Apple", 10)`` and ``other`` is ``Fruit("Cherry", 5)`` returns ``False`` (because the ``price`` of the apple is not less than the price of the cherry) so this means ``Cherry`` should come before ``Apple`` in the sorted list. If we wanted to sort by names, we could define ``__lt__`` differently. *Note that when we call ``<`` on strings, it does an alphabetical comparison; ``"Apple" < "Cherry"`` is ``True``. We can take advantage of this in our ``__lt__`` method*: @@ -110,8 +111,8 @@ If we wanted to sort by names, we could define ``__lt__`` differently. *Note tha def __lt__(self, other): # other is another instance of Fruit return self.name < other.name # note we are comparing names - cherry = Fruit("Cherry", 10) - apple = Fruit("Apple", 5) + apple = Fruit("Apple", 10) + cherry = Fruit("Cherry", 5) blueberry = Fruit("Blueberry", 20) L = [cherry, apple, blueberry] @@ -119,6 +120,7 @@ If we wanted to sort by names, we could define ``__lt__`` differently. *Note tha for f in sorted(L): print(f.name) - print(blueberry < cherry) # Equivalent to blueberry.__lt__(cherry) ; True + print(f'apple < cherry: {apple < cherry}') # Equivalent to apple.__lt__(cherry) ; False -Finally, note that if we pass in a ``key`` to ``sorted()`` (approach 1), it will use that instead of calling the ``__lt__`` method. \ No newline at end of file + +Finally, note that if we pass in a function for the ``key`` parameter when we call ``sorted()`` (approach 1), it will use that key function instead of calling the ``__lt__`` method. You can try putting a print statement inside the ``__lt__`` method to see this for yourself: __lt__ will not be called when you provide a key function but it will be called when you don't provide a key function. \ No newline at end of file