Ecoturtle
Ecoturtle

Reputation: 455

How to get __lt__() to sort

class City:
    def __init__(self, string):
        self._string = string.split(',')
        self._name = self._string[0]
        self._state = self._string[1]
        self._latitude = self._string[2]
        self._longitude = self._string[3]
        self._location = [self._latitude, self._longitude]

    def name(self):
        return self._name

    def location(self):
        return self._location
        self.hand.sort(key=lambda x: x.longitude)

    def __lt__(self, other):
        if self._longitude < other._longitude:
            return True
        if self._longitude > other._longitude:
            return False
        if self._longitude == other._longitude:
            if self._latitude < other._latitude:
                return True
            if self._latitude > other._latitude:
                return False

citystrings = ["Charleston,WV,38.35,81.63",
              "Charlotte,NC,35.23,80.83",
              "Cheyenne,WY,41.15,104.87",
              "Chicago,IL,41.83,87.62",
              "Cincinnati,OH,39.13,84.50",
              "Cleveland,OH,41.47,81.62",
              "Columbia,SC,34.00,81.03",
              "Columbus,OH,40.00,83.02",
              "Dallas,TX,32.77,96.77",
              "Denver,CO,39.75,105.00"]
westtoeastnames = [
            "Denver",
            "Cheyenne",
            "Dallas",
            "Chicago",
            "Cincinnati",
            "Columbus",
            "Charleston",
            "Cleveland",
            "Columbia",
            "Charlotte",
          ]
cities = [City(s) for s in citystrings]
cities.sort()
sortednames = [c.name() for c in cities]
print(sortednames)
print(westtoeastnames)
['Cheyenne', 'Denver', 'Charlotte', 'Columbia', 'Cleveland', 'Charleston', 'Columbus', 'Cincinnati', 'Chicago', 'Dallas']
['Denver', 'Cheyenne', 'Dallas', 'Chicago', 'Cincinnati', 'Columbus', 'Charleston', 'Cleveland', 'Columbia', 'Charlotte']

This code tries to use __lt__() to sort the cities by how far they are west and the longitudes are based west of the prime meridian. I wrote an __lt__() method in the class but the citystrings won't sort to the correct order.

Upvotes: 0

Views: 640

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1123930

You are comparing your longitude and latitude as strings, not numbers. They are therefor compared lexicographically, not numerically, so '104' will sort before '80' because '1' comes before '8' in the ASCII table (it doesn't matter what other characters follow).

Convert your values to floating point numbers:

self._latitude = float(self._string[2])
self._longitude = float(self._string[3])

Your comparison has a small bug; if both longitude and latitude match, you return None instead of False. You may want to test for equality and apply the @functools.total_ordering() decorator rather than assume that only __lt__() is called.

Cleaning up the code a little (and removing the name() and location() methods, just use name and location attributes):

from functools import total_ordering

@total_ordering
class City:
    def __init__(self, string):
        self.name, self.state, lat, long = string.split(',')
        self.location = (self._latitude, self._longitude) = float(lat), float(long)

    def __lt__(self, other):
        if not isinstance(other, City):
            return NotImplemented
        # tuples defer ordering to the contents; compare them
        # in (longitude, latitude) order so that if longitude is 
        # equal, the outcome is based on latitude.
        return self.location[::-1] < other.location[::-1]

    def __eq__(self, other):
        if not isinstance(other, City):
            return NotImplemented
        return self.location == other.location

Note that __lt__() really only needs to compare self.location; tuple ordering takes care of the rest:

sortednames = [c.name for c in sorted(map(City, citystrings), reverse=True)]

Note the reverse=True; you want the larger values (further west from Greenwich) to be listed first.

Upvotes: 6

Related Questions