2

In Python you can for example sort tuples sorted([(2,2),(1,2),(2,1),(1,1)]) and get [(1, 1), (1, 2), (2, 1), (2, 2)].

You can also use custom key functions sorted([x, y, z], key=custom_key) to implement special sorting criteria.

For example:

medals_map = {'Gold': 1, 'Silver': 2, 'Bronze': 3 }
def custom_key(item):
    if isinstance(item, basestring):
       try:
           return medals_map[item]
       except KeyError:
           pass
    return item

Could sort any list of strings normally, and also gold/silver/bronze according to my custom ordering.

But it does not "correctly" handle tuples anymore: sorted([('Gold', 2), ('Bronze', 1), ('Gold', 1)], key=custom_key) results in [('Bronze', 1), ('Gold', 1), ('Gold', 2)] as item is not a basestring.

But I would like to get [('Gold', 1), ('Gold', 2), ('Bronze', 1)] by somehow applying the custom logic to each item in the tuple.

What's the best way to "map" the custom key logic over any tuples (that may or may not appear) in the list like the default key function does?


Edit: More examples:

  • sorted(['Silver', 'Bronze', 'Gold'], key=custom_key)
    • ['Gold', 'Silver', 'Bronze']
  • sorted([['Silver', 2], ['Silver', 1], ['Gold', 1]], key=custom_key)
    • [['Gold', 1], ['Silver', 1], ['Silver', 2]]
  • sorted([(2, 'Gold'), (2, 'Bronze'), (1, 'Gold')], key=custom_key)
    • [(1, 'Gold'), (2, 'Gold'), (2, 'Bronze')]
  • sorted([('Silver', 'Bronze'), ('Gold', 'Bronze'), ('Silver', 'Gold')], key=custom_key)
    • [('Gold', 'Bronze'), ('Silver', 'Gold'), ('Silver', 'Bronze')]
5
  • 2
    So the custom_key needs to handle item being either a tuple or string? Why not add another isinstance, with a recursive call? Note that return medals_map.get(item, item) is neater than faffing with try and except. Commented Sep 2, 2015 at 11:14
  • @jonrsharpe: Right, but item might also be a list, or maybe some other iterable? Do I just have to add more isinstance checks for all kinds of things? I hoped since the default key function already does all that I can somehow avoid doing that. Commented Sep 2, 2015 at 11:39
  • 1
    @Peter, add an example of a less structured input list and what you expect as output Commented Sep 2, 2015 at 11:43
  • @PadraicCunningham: I now added more examples at the end of the question Commented Sep 2, 2015 at 11:59
  • @Peter if you don't even know what you're going to be trying to sort, maybe solve that problem? Commented Sep 2, 2015 at 12:11

4 Answers 4

2
from collections import Iterable
def custom_key(item):
    if isinstance(item, Iterable) and not isinstance(item, basestring):
        return [medals_map.get(ele, ele) for ele in item]
    return medals_map.get(item, item)

Output:

In [2]: assert sorted(['Silver', 'Bronze', 'Gold'], key=custom_key) == ['Gold', 'Silver', 'Bronze']

In [3]: assert sorted([['Silver', 2], ['Silver', 1], ['Gold', 1]], key=custom_key) == [['Gold', 1], ['Silver', 1], ['Silver', 2]]

In [4]: assert sorted([(2, 'Gold'), (2, 'Bronze'), (1, 'Gold')], key=custom_key) == [(1, 'Gold'), (2, 'Gold'), (2, 'Bronze')]

In [5]: assert sorted([('Silver', 'Bronze'), ('Gold', 'Bronze'), ('Silver', 'Gold')], key=custom_key) == [('Gold', 'Bronze'), ('Silver', 'Gold'), ('Silver', 'Bronze')]
Sign up to request clarification or add additional context in comments.

2 Comments

This looks interesting. Why is the first element in the iterable special?
@Peter, it's not, was originally doing something else, you just need to return the list come if the "item" is not a string.
1

You could create a mapper function, that takes your key function and returns another function, applying the key function to each element of some iterable.

def mapper(function):
    def inner(values):
        return tuple([function(x) for x in values])
    return inner

Example:

>>>sorted([('Gold', 2), ('Bronze', 1), ('Gold', 1)], key=mapper(custom_key))
[('Gold', 1), ('Gold', 2), ('Bronze', 1)]

Or similar, using functools.partial with map:

>>> sorted([('Gold', 2), ('Bronze', 1), ('Gold', 1)], key=functools.partial(map, custom_key))
[('Gold', 1), ('Gold', 2), ('Bronze', 1)]

1 Comment

I don't actually know the structure of the list. It might not contain tuples.
1

The key function can return a tuple:

medals_map = {'Gold': 1, 'Silver': 2, 'Bronze': 3 }

def custom_key(item):
    if isinstance(item, basestring):
       try:
           return medals_map[item]
       except KeyError:
           pass
    return item

print sorted([('Gold', 2), ('Bronze', 1), ('Gold', 1)], key=lambda x: (custom_key(x[0]), x[1]))

Prints [('Gold', 1), ('Gold', 2), ('Bronze', 1)]. Essentially this makes the sorting behave the same as your first example, i.e. sorted([(1, 2), (3, 1), (1, 1)]).

Edit: you can also make a custom key that returns a tuple, which may be prettier.

1 Comment

I don't actually know the structure of the list. It might not contain tuples, and the medals might appear in any position in the tuple.
0

You don't need a extra function. You can also do it:

medals_map = {'Gold': 1, 'Silver': 2, 'Bronze': 3 }
List = [('Bronze', 1), ('Gold', 1), ('Gold', 2)]

new_list = sorted(List, key=lambda word: (medals_map[word[0]], word[1]))

print new_list

Output:

[('Gold', 1), ('Gold', 2), ('Bronze', 1)]

5 Comments

That works for this specific example, but I don't actually know the structure of the list. It might not contain tuples, and the medals might appear in any position in the tuple.
But this answer according this question. you don't tell this(as your comment) in you question. So my answer according question
The question mentions "over any tuples (that may or may not appear)". (I did not downvote your answer by the way!)
Hmm you edited your question only 6 min ago, but I answered 28 min ago
Only to add more examples. The quoted part of the question was there before.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.