-1

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!

zeval
  • 147
  • 1
  • 10

2 Answers2

2

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

nosklo
  • 217,122
  • 57
  • 293
  • 297
  • Actually that's not exactly what I'm looking for, I'm fairly certain what I want can be done within *key* itself, I just don't know how, but the problem here isn't exactly using my functions. I can do that. The problem is that I'm not sure how to sort the list in a way that places elements with the string I'm asking for first. – zeval Dec 10 '19 at 18:08
  • I'm sorry, coming back to your reply I realize that it is more appropriate than I had previously thought. But I'm not exactly following what you attempt to get/how you're thinking of using your _key function, could you please elaborate? I believe this is exactly what I'm looking for just not in it's current state. Also I'm not sure if through this method I can find a way to sort the list by presence of "NYC" or "NJ" or whatever string I want, but perhaps I'm misinterpreting it, could you specify? – zeval Dec 10 '19 at 20:09
1

With numbers, you can reverse the sorting on some of the keys but not others by selectively using a minus sign, e.g. vectors.sort(key=lambda v: (v.x, -v.y)) 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, e.g. 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.

kaya3
  • 47,440
  • 4
  • 68
  • 97
  • Thank you! The technique involving the minus sign is exactly what I was looking for. Regarding the rest, I was looking to sort the list based on a given string *and* sort the list with some *key* parameters in reverse, but not both simultaneously. Also, I have already assigned names to the index values. Thankfully, I am not trying to work with numeric index values everywhere, that'd be a mess. Thank you for the input! Exactly what I was looking for regarding the bonus question. – zeval Dec 10 '19 at 18:13
  • I've edited to give an example of sorting `'NYC'` to the start of the list using a custom key. – kaya3 Dec 10 '19 at 18:14
  • It's a logical approach, I would definitely study it and used it here, but I am *really* trying to handle this *string sorting* issue with something a bit more simple, something also involving the use of lambda. Thank you. – zeval Dec 10 '19 at 18:31