Python - How to sort a list of lists by last eleme

2019-09-02 20:53发布

问题:

I have a list of n value vectors and a list of n time vectors. I would like to sort the time vectors based on the last element of each value vector. I thought of appending to each time vector the last element of the corresponding value vector, then using

time_vector.sort(key=lambda x: x[-1]) #or
time_vector.sort(key=itemgetter(-1))

and finally popping out the last element of each time vector. However, I'm thinking there must be a simpler manner to do this. I haven't been able to find any example for this problem in the Python reference for sort and sorted.

Expanded Question using Alex-Martelli's proposed solution. Because I have to reorder both value vectors v_v and time vectors t_v, Alex offers a one-stop solution

(v_v, t_v) = zip(*sorted (zip(v_v, t_v),key=lambda v: v[0][-1]))

I do not understand how unzipping works, but I just copied it from Python's documentation, and it works. The additional question is why this doesn't work using itemgetter() instead of lambda, and why it doesn't work with the vector.sort() method instead of using the function sorted()

The numerical examples I used to test the above are

v_v = [[1, 2], [2, 3], [3, 1], [5, 3, 4]]
t_v = [[1, 1], [5, 6], [9, 8], [3, 10, 1]]

which produce the sorted result of

v_v = ([3, 1], [1, 2], [2, 3], [5, 3, 4])
t_v = ([9, 8], [1, 1], [5, 6], [3, 10, 1])

回答1:

Since the two vectors are -- and apparently need to remain! -- separate, this is a case for good old DSU -- decorate, sort, undecorate.

result = [t for v, t in
          sorted(zip(value_vector, time_vector), key=lambda vt: vt[0][-1])]

The addition of the key= arg has almost done away with the need to understand DSU in most cases -- it does the decoration for you (as long as it's just a function of each item) and the undecoration after the sort too.

But here, the sort key is not just a function of the item -- it's a function of the item's position in the list, which you use to index a separate parallel list (a very unusual arrangement of data in Python). Yet you want to sort the times (even though per se they have absolutely nothing to do with the desired order!) -- that's the crux of the difficulty.

Alternatively, consider sorting the positions, then using those to reorder the times. That may feel a bit less weird:-). It can be collapsed into a single statement, but it may be clearer when presented as two:

idx_val = sorted(enumerate(value_vector), key=lambda i, v: v[-1])
result = [times_vector[i] for i, _ in idx_val]

I think this may be clearer because the idx_val (properly sorted list of (index, value) pairs where we don't actually care about the value further but need it for the sorting) isn't "glued on" to the times then has to be ripped off again -- rather we just use the indices in the list comprehension. And the naming and the splitting into two steps helps.

(But of course if you're a "one statement to rule them all" fiend you can substitute the sorted call into the listcomp in lieu of the idx_val name and get back to a single statement -- no real advantage at all, but, to the wrong sort of people, "cooler":-)]

Added: another guise of DSU would be an OrderedDict -- just a different way to "undecorate", really. But there's a commenter mentioning it, so here it is -- you be the judge of whether it offers any value.

import collections
od = collections.OrderedDict(
    sorted(zip(value_vector, time_vector),
           key=lambda v, t: v[-1]))
result = list(od.values())

I actually think that by hiding the undecorating in the keys/values play of a dict subclass, it makes things murkier. But, de gustibus...