simi
simi

Reputation: 1555

Sorting a list of dictionaries of objects by dictionary values

This is related to the various other questions about sorting values of dictionaries that I have read here, but I have not found the answer. I'm a newbie and maybe I just didn't see the answer as it concerns my problem.

I have this function, which I'm using as a Django custom filter to sort results from a list of dictionaries. Actually, the main part of this function was answered in a related question on stackoverflow.

def multikeysorting(dict_list, sortkeys):
    from operator import itemgetter

    def multikeysort(items, columns):
        comparers = [ ((itemgetter(col[1:]), -1) if col.startswith('-') else (itemgetter(col), 1)) for col in columns]

        def sign(a, b):
            if a < b:   return -1
            elif a > b: return 1
            else:       return 0

        def comparer(left,right):
            for fn, mult in comparers:
                result = sign(fn(left), fn(right))
                if result:
                    return mult * result
            else:
                return 0

        return sorted(items, cmp=comparer)

    keys_list = sortkeys.split(",")
    return multikeysort(dict_list, keys_list)

This filter is called as follows in Django:

{% for item in stats|statleaders_has_stat:"TOT_PTS_Misc"|multikeysorting:"-TOT_PTS_Misc.value,TOT_PTS_Misc.player.last_name" %}

This means that there are two dictionary values passed to the function to sort the list of dictionaries. The sort works with the dictionary keys, but not the values.

How can I sort the and return the dictionary by sorting the list of dictionaries with more than one value? In the example above, first by the value, then by the last_name.

Here is an example of the data:

[{u'TOT_PTS_Misc': < StatisticPlayerRollup: DeWitt, Ash Total Points : 6.0>, 'player': < Player: DeWitt, Ash>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Ackerman, Luke Total Points : 18.0>, 'player': < Player: Ackerman, Luke>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Wise, Dan Total Points : 19.0>, 'player': < Player: Wise, Dan>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Allison, Mike Total Points : 18.0>, 'player': < Player: Allison, Mike>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Wolford, Alex Total Points : 18.0>, 'player': < Player: Wolford, Alex>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Okes, Joe Total Points : 18.0>, 'player': < Player: Okes, Joe>}, 
{u'TOT_PTS_Misc': < StatisticPlayerRollup: Grattan, Paul Total Points : 18.0>, 'player': < Player: Grattan, Paul>}]
    

The listing should be sorted as follows:

LastName Points
Wise 19.0
Ackerman 18.0
Allison 18.0
Grattan 18.0
Okes 18.0
Wolford 18.0
Hagg 6.0
DeWitt 6.0

The TOT_PTS_Misc is an object that contains the player name as well as the number of points. (I hope I am explaining this correct.)

But, there should be an arbitrary sort of values, either ascending or descending. Not always that same values and possibly more than two.


So I came up with this solution, but wanted to know if it makes sense and if there is anything that should be changed.

def multikeysorting(dict_list, sortkeys):
    from operator import itemgetter, attrgetter

    klist = sortkeys.split(",")
    vlist = []
    for i in klist:
        vlist.append(tuple(i.split(".")))

    def getkeyvalue(val_list):
        result = []
        for id,val in enumerate(val_list):
            if val[0].startswith('-'):
                if len(val) == 2:
                    result.append((itemgetter(val[0][1:]).attrgetter(val[1]), -1))
                else:
                    att = val[1]
                    for j in val[2:]:
                        att = att + "." + j
                    result.append((itemgetter(val[0][1:]).attrgetter(att), -1))
            else:
                if len(val) == 2:
                    result.append((itemgetter(val[0]).attrgetter(val[1]), 1))
                else:
                    att = val[1]
                    for j in val[2:]:
                        att = att + "." + j
                    result.append((itemgetter(val[0]).attrgetter(att), 1))
        return result

    return sorted(dict_list, key=getkeyvalue(vlist))

Upvotes: 2

Views: 1885

Answers (2)

ars
ars

Reputation: 123468

You gain access to the keys using itemgetter and to the value attributes using attrgetter.

So, once you've extracted the key, value names you're interested in, you can construct your key function:

from operator import attrgetter, itemgetter
itmget = itemgetter('TOT_PTS_Misc')
attget_v = attrgetter('value')
attget_l = attrgetter('last_name')
def keyfunc(x):
    itm = itmget(x)
    return (-attget_v(itm), attget_n(itm))
sorted(dictlist, key=keyfunc)

This seems to work. Is it what you're asking? Or am I missing something?

Upvotes: 2

OmerGertel
OmerGertel

Reputation: 2573

As far as I can see, there are two things you need to do.

First, parse the call path out of the sort key, that is: turn 'TOT_PTS_Misc.value' to ('TOT_PTS_Misc','value') Second, use attrgetter in a similar way to the use of itemgetter, for the callable part.

If i'm not mistaken, itemgetter('TOT_PTS_Misc').attrgetter('value') SHOULD be equal to dict['TOT_PTS_Misc'].value

Upvotes: 0

Related Questions