0

Having (MVCE) list data like below, I would like to sort it first by x coordinate, and later by y coordinate.
I have tried:

import operator
data = [
    { "coords": [142, -42]},
    { "coords": [147, -42]},
    { "coords": [151, -41]},
    { "coords": [147, -41]},
    { "coords": [149, -44]},
    { "coords": [150, -41]},
    { "coords": [149, -40]},
    { "coords": [150, -42]},
    { "coords": [151, -40]}
]

k1 = operator.itemgetter("coords")
# i've also tried various combinations like
#k2 = lambda data: operator.itemgetter(data["coords"][0]),\
#                  operator.itemgetter(data["coords"][1])
# but TypeError: list indices must be integers, not str

e = []
for d in sorted(data, key=k1):
    e.append(d)
print("\n".join([str(s) for s in e]))

but this gives data being only sorted by X, but not Y next.

>>> { "coords": [142, -42]}
    { "coords": [147, -42]}
    { "coords": [147, -41]}
    { "coords": [149, -44]}
    { "coords": [149, -40]}
    { "coords": [150, -42]}
    { "coords": [150, -41]}
    { "coords": [151, -41]}
    { "coords": [151, -40]}

I'm aware that I can pass more than one argument to itemgetter. Is there a way to sort this in one statement?

(Desired result)

>>> { "coords": [142, -42]}
    { "coords": [147, -41]}
    { "coords": [147, -42]}
    { "coords": [149, -40]}
    { "coords": [149, -44]}
    { "coords": [150, -41]}
    { "coords": [150, -42]}
    { "coords": [151, -40]}
    { "coords": [151, -41]}

Data has to be added to e list, as this is part of larger processing.

Not a duplicate of sort-list-of-dictionaries-by-another-list or similar that I managed to found here. Most people want to sort list of dictionaries by two values from dict, and here I want to sort by two values from nested list.

kivy_student
  • 150
  • 1
  • 9
  • Possible Duplicate of [Sort a list by multiple attributes?](http://stackoverflow.com/q/4233476) – Bhargav Rao Jun 08 '16 at 18:05
  • @BhargavRao Not a duplicate, see my edit - last paragraph. – kivy_student Jun 08 '16 at 18:10
  • 1
    Did you check the dupe? It's essentially the same. You can modify the dupe to tailor your problem like this `sorted(data, key=lambda x:(x['coords'][0],x['coords'][1]))`. I just added a dict lookup to the dupe. – Bhargav Rao Jun 08 '16 at 18:13
  • If you are looking for a descending sort, Then see [Python sort list of lists / ascending and then decending](http://stackoverflow.com/q/6666748) – Bhargav Rao Jun 08 '16 at 18:14
  • I did, as well as several others (Your link -> `[('Al', 2),('Bill', 1),('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]`, list of tuples), perhaps I failed to come up with similarities. – kivy_student Jun 08 '16 at 18:15
  • I changed the link midway, If that was the reason why you got confused. – Bhargav Rao Jun 08 '16 at 18:16
  • Possible duplicate of [Sort a list by multiple attributes?](http://stackoverflow.com/questions/4233476/sort-a-list-by-multiple-attributes) – wwii Jun 08 '16 at 18:20
  • Also see http://stackoverflow.com/questions/2878084/sort-a-list-of-dicts-by-dict-values . Note that the OP doesn't actually want a mixed ascending & descending sort, that was just due to sign confusion, as mentioned in their comment on Scott Hunter's answer. – PM 2Ring Jun 08 '16 at 18:25
  • BTW, your code for creating `e` can be simplified: `e = sorted(data, key=operator.itemgetter("coords"))`. – PM 2Ring Jun 08 '16 at 18:31
  • @PM2Ring thx, I actually have it like this, but due to neg.sign I thought data were not sorted. Anyway problem observed and solved by Scott. – kivy_student Jun 08 '16 at 18:36
  • You should use `.itemgetter` though, as it's faster than using a `lambda`. – PM 2Ring Jun 08 '16 at 18:37
  • I might do some timeit to confirm that, but for now data size is too small for this optimization to matter. Anyway, How would You write `operator.itemgetter("coords")` if I had three coords and wanted to sort by first and next by third ? – kivy_student Jun 08 '16 at 18:38
  • For that, you _would_ use a lambda. Eg, `lambda x: x["coords"][0::2]` – PM 2Ring Jun 08 '16 at 18:39

1 Answers1

3

It is sorted properly; -42 < -41, for example.

To get the order you seem to want, you could negate the y coordinate:

k1 = lambda x: (x['coords'][0], -x['coords'][1])
Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • ... you are right. Negative sign got me confused. Yet, You should probably leave it If someone wanted sort ascending by first, and descending by other. – kivy_student Jun 08 '16 at 18:13