tags:

views:

348

answers:

2

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))
A: 

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

OmerGertel
Thank you. This is what I'm having trouble understanding I guess. I know that if I do this, the sorting works: sorted(dict_list, key=lambda d: (-d['TOT_PTS_Misc'].value,d['TOT_PTS_Misc'].player.last_name))Now all I have to do is figure out how I can code this for any values and however many values.
simi
OK. So I got this so far:def multikeysorting(dict_list, sortkeys): from operator import itemgetter keys_list = sortkeys.split(",") values_list = [] for item in keys_list: values_list.append(item.split("."),) return sorted(dict_list, key=lambda d: (-d['TOT_PTS_Misc'].value,d['TOT_PTS_Misc'].player.last_name))How can I do an inline for loop with values_list?
simi
I'm getting an error that 'operator.itemgetter' object has no attribute 'attrgetter'.
simi
+1  A: 

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?

ars
Close, but what if I want for any arbitrary keys and values and any number of them. What I'm working on is first split the string of the arbitrary values with: keys_list = sortkeys.split(","). Then, in a for loop, set up another list of tuples that splits them into key, value pairs with this: for item in keys: values_list.append(item.split("."),). Now, can I use this to return the sorted list? return sorted(dict_list, key=lambda d: (((itemgetter(colk[1:]).attrgetter(colv), -1) if col.startswith('-') else (itemgetter(colk).attrgetter(colv), 1)) for colk,colv in values_list)?
simi