简体   繁体   中英

How do I sort a list based on the presence of a string?

I currently have a list of lists, something of this kind:

[['NJ', '10', '2000', '500', '20', '02-03-19', '15:20'], 
 ['NJ', '15', '1500', '600', '20', '02-03-19', '15:30'], 
 ['NYC', '25', '1500', '500', '10', '02-03-19', '15:30'], 
 ['NYC', '15', '1200', '700', '1', '02-03-19', '15:35']]

And I need to sort them with several elements in mind, for example, let's say, in terms of index numbers, 0 is area, 1 is weight, 2 is distance, 3 is height, 4 is autonomy, 5 is date and 6 is a timestamp. I am already sorting all of the elements using this:

list.sort(key=itemgetter(0, time_sorter, 4, 3))

Where time_sorter() is a function I had previously built that sorts lists based on element time stamps. My problem is that I need to sort this list with "area" in mind. For example, say I need to sort the list with all those elements in mind, as I am, but I would also like to sort it, simultaneously, in way that the elements which have "NYC" on their 0 index position are placed first, how do I go about this?

Bonus question would be: If I have multiple parameters in itemgetter() or the sort "key" parameter itself, and I want the sorting to be reverse but for only one of those arguments, how do I go about that?

Is my best option really to separate all these sorts into several functions and call those in the sort key?

Thanks in advance!

Make a function that returns the sort key for each item. The function will be called multiple times, each time with a single item as parameter, and you can call other functions inside it if you want.

def _key(row):
    return row[0], time_sorter(row), row[4], row[3]

Whatever you return will be used to sort the items:

mylist.sort(key=_key)

itemgetter is just a simple function factory that creates a function to get items. If you need anything more complex you should make your own function

With numbers, you can reverse the sorting on some of the keys but not others by selectively using a minus sign, eg vectors.sort(key=lambda v: (vx, -vy)) sorts a list of vectors first by their x component in ascending order, then by y component in descending order as a tie-breaker. Unfortunately, there is no equivalent way to do this with strings.

A good option is to write a class to represent your data as objects instead of lists, and define a custom ordering on your class. The functools.total_ordering decorator makes this easier, so you only have to define __eq__ and __lt__ instead of all six comparison methods:

from functools import total_ordering

@total_ordering
class PlaceAndTime:
    def __init__(self, area, weight, distance, height, autonomy, date, time):
        self.area = area
        self.weight = weight
        self.distance = distance
        self.height = height
        self.autonomy = autonomy
        self.date = date
        self.time = time

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

    def __lt__(self, other):
        # test whether self < other
        if not isinstance(other, PlaceAndTime):
            raise ValueError('Not comparable')
        elif self.area != other.area:
            # sort by area in reverse order using > instead of <
            return self.area > other.area
        elif self.date != other.date:
            return self.date < other.date
        elif self.time != other.time:
            return self.time < other.time
        elif self.autonomy != other.autonomy:
            # sort by autonomy in reverse order using > instead of <
            return self.autonomy > other.autonomy
        else:
            return self.height < other.height

This way you can sort a list of objects simply using objects.sort() without providing a key. If you specifically want to sort one area like 'NYC' to the start of the list, you can still use a custom key:

objects.sort(key=lambda obj: (obj.area != 'NYC', obj))

This works because when obj.area == 'NYC' the comparison will be False , otherwise it will be True , so the comparison False < True means 'NYC' will appear before other areas.

Writing a class likely has other benefits too, eg it lets you write obj.time instead of the less-descriptive lst[6] , and there may be other methods you can write which simplify other parts of your code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM