0

In SQL, we can specify desc/asc order for each sorting key, i.e. order by key1 desc, key2 asc. How can I acheive the same thing to sort a list of tuples in python?

Suppose I have a list like

[("a", "b", "e"), ("b", "a", "c"), ("b", "a", "d")] 

I want to use the second key in asc order and the third ky in desc order. So the result is

[("b", "a", "d"), ("b", "a", "c"), ("a", "b", "e")] 

Note, the elements can be any type that has comparison methods (i.e. __lt__, __gt__ etc)

8
  • 1
    You can try with this, sorted(sorted(l, key=lambda x:x[1]), key=lambda x:x[2], reverse=True). There may be a better solution than this! Commented Feb 27, 2020 at 7:12
  • This might help. stackoverflow.com/questions/3121979/… Commented Feb 27, 2020 at 7:12
  • @shaikmoeed There's indeed a better solution than that, namely one that does it correctly :-P Commented Feb 27, 2020 at 16:18
  • @HeapOverflow I felt using sort for two times is redundant. There may be a better and generic solution than this ;-) !!! Commented Feb 28, 2020 at 4:48
  • 1
    @HeapOverflow As I said before, this solution is redundant(as it is not scalable when tuple consists of more elements to sort in different orders). Yes, this can be the best one for this specific post, but I haven't tested the timings though. Commented Feb 28, 2020 at 12:20

3 Answers 3

3

You need to build a suitable key function for sorting - see Sort tuples based on second parameter and plenty of others

data = [("a", "b", "c"), ("b", "a", "c"), ("b", "a", "d")] s = sorted(data, key = lambda x: (x[1],-ord(x[2]))) # "trick" to sort descending print (s) 

gives

[('b', 'a', 'd'), ('b', 'a', 'c'), ('a', 'b', 'c')] 

You use a "customized" tuple as key and need to be a bit creative building it.

The ord funciton gives you the ascii value of the character, by negating it you "sort descending" on it.

If you sort more then letters you need to be even more creative.


Not seriously tested:

data = [("a", "b", "cesa"), ("b", "a", "ceta"), ("b", "a", "derived")] s = sorted(data, key = lambda x: (x[1], tuple((-ord(t) for t in x[2])) ) ) 

to get

[('b', 'a', 'derived'), ('b', 'a', 'ceta'), ('a', 'b', 'cesa')] 
Sign up to request clarification or add additional context in comments.

3 Comments

@patric-artner Thank you for answer. So what if the elements are more than two letters? I can't use ord function in this case.
@shi you still can, but you need to do it for all letters - no need to adjust x[1] in that case in a similar matter because it sorts ascending fine
Thanks again. I updated my question. In fact I wanted to ask a more general case, not only string or numbers. I want to tweak sorting order for any comparable objects.
1

You can use two sorts (they're stable) in order of decreasing priority:

>>> from operator import itemgetter >>> a = [("a", "b", "e"), ("b", "a", "c"), ("b", "a", "d")] >>> a.sort(key=itemgetter(2), reverse=True) >>> a.sort(key=itemgetter(1)) >>> a [('b', 'a', 'd'), ('b', 'a', 'c'), ('a', 'b', 'e')] 

Last time I checked, this was btw faster than a single sort with a more complicated key function. And it takes less memory than a key function creating new tuples.

See Python's Sorting HOW TO for more examples of this.

1 Comment

Thank you so much! This is exactly what I was looking for.
1

I found a way where I create a sorter class that has custom comparison methods. This is not an efficient way but works fine.

from functools import total_ordering def _apply(v, k): if callable(k): return k(v) else: return v[k] def smart_sort(xs, getters_and_orders): orders = [] getters = [] for x in getters_and_orders: if isinstance(x, tuple): g, o = x else: g = x o = "desc" getters.append(g) orders.append(o) @total_ordering class _Sorter: def __init__(self, v): self._values = [_apply(v, g) for g in getters] def __eq__(self, other): return self._values == other._values def __lt__(self, other): for (a, b, o) in zip(self._values, other._values, orders): if a != b: return a > b if o == "desc" else a < b return False return sorted(xs, key=_Sorter) xs = [("a", "b", "e"), ("b", "a", "c"), ("b", "a", "d")] ret = smart_sort(xs, [(1, "asc"), (2, "desc")]) print(ret) >> [('b', 'a', 'd'), ('b', 'a', 'c'), ('a', 'b', 'e')] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.