-3

I have a class list containing objects. I want to sort the class list based on two of the attributes of the objects. Some of the attributes will be of equal value, in which case I want to sort them based on a secondary attribute. How would I do this? My stuff looks like

f.cards
Out[95]: 
[<__main__.PlayingCard at 0x56d44e0>,
 <__main__.PlayingCard at 0x56d4438>,
 <__main__.PlayingCard at 0x56d4588>,
 <__main__.PlayingCard at 0x56d4390>,
 <__main__.PlayingCard at 0x56d4828>,
 <__main__.PlayingCard at 0x56d4400>,
 <__main__.PlayingCard at 0x56d4358>]
f.cards[0].give_value()
Out[96]: 14
f.cards[0].getSuit()
Out[97]: 'Diamonds'

f.cards[2].give_value()
Out[100]: 14
f.cards[2].getSuit()
Out[101]: 'Hearts'

My intent is to define a sorting function, so that I can sort the cards by primarily their value, and secondarily their suit. Such as the example printout, where I would like the card with value 14 to be placed first as it has the suit 'Hearts'

My last attempt was this:

elements = sorted([(o.give_value(), o.suit) for o in cards], reverse=True)
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
dan479
  • 43
  • 3
  • what have you done ? – Mohsen_Fatemi Feb 09 '18 at 16:51
  • where is your code ? – Mohsen_Fatemi Feb 09 '18 at 16:51
  • Possibly a dupe of https://stackoverflow.com/questions/4233476/sort-a-list-by-multiple-attributes – Paulo Scardine Feb 09 '18 at 16:55
  • What part of the code are you asking for specifically? The code that needs the sorted objects has not yet been written, as I need to have it sorted first. My own attempts at sorting it haven't yielded anything worthwhile sharing. I'm thinking keys might be the thing to use here but I am not yet proficient enough at python to make them work they way I want, and I haven't found any piece of code online that I can easily enough adapt to suit me – dan479 Feb 09 '18 at 16:55
  • 1
    Please share what you have done so far, the current results and the expected result even if you think it is not worth sharing. – Paulo Scardine Feb 09 '18 at 16:56
  • elements = sorted([(o.give_value(), o.suit) for o in cards], reverse=True) is what I was using until I discovered I needed suits sorted as well – dan479 Feb 09 '18 at 16:57
  • Try this: `elements = sorted(cards, key=lambda o: (o.give_value(), o.suit), reverse=True)`. I was only able to come up with that because you shared your code. This will sort the suits alphabetically- if that's not what you want, you need to specify the order you want in your question. – pault Feb 09 '18 at 16:58

2 Answers2

2

index will find the index of a given element in the list/tuple it's invoked on. So e.g. Clubs will be mapped to 0, Hearts to 2. Because we sort by tuple, the second element is only taken into consideration when the first is equal in both cards.

sorted(
    cards,
    key=lambda x: (
        x.give_value(),
       ('Clubs', 'Diamonds', 'Hearts', 'Spades').index(x.getSuit())
    ),
    reverse=True,
)

Edit: Fixed the typo and changed [...] to (...) as suggested in the comments.

kszl
  • 1,203
  • 1
  • 11
  • 18
  • 1
    Exactly what I needed! Thank you. The only thing I had to edit was reversed=True, should be reverse=True – dan479 Feb 09 '18 at 17:08
  • 2
    if you make the list of strings into a tuple with (), it will get built once when the function is 'compiled'; if it's a list with [], the list object is constructed each time the lambda is called. – greggo Feb 09 '18 at 17:22
0

In python2, you can use optional parameter cmp of function sorted to define a custom comparison function, like this:

def cmp(x, y):
    if x.attr_a != y.attr_a:
        return x.attr_a - y.attr_a
    else:
        return x.attr_b - y.attr_b
smilingwang
  • 119
  • 6
  • 2
    One thing to be aware of - this 'cmp' function is called every time the sort algo needs to compare two things. If you do it by defining a key function, as in BUZZY's answer, and then using the default compare on the key function result, then the key function is only called once per item in the list, and those key values kept in a list internally during the sort. So, for a long list, use of 'cmp' will use less memory but could be a lot slower. – greggo Feb 09 '18 at 17:47
  • @greggo You are right, we can use `functools.cmp_to_key` to convert `cmp` to `key` – smilingwang Feb 10 '18 at 01:40
  • Well `cmp_to_key` will make it use more memory, and be slower, relative to using cmp=. `cmp_to_key` returns a 'key' function that just binds the object to the comparison function, making a 'key'; when two of these are compared, the comparison function will be called. So it adds overhead and it still will do a lot of calls to the compare func (`cmp_to_key` was introduced because py3 drops the cmp= option in sorts). For best performance, your key function should return a simple object (a number, string, or a tuple of these types), and the built-in python compare will compare those efficiently. – greggo Feb 15 '18 at 18:11