Complex sorting easily done with the cmp function, but how do I plan to use Python 3?

I want to sort the columns returned from a database query for a view. In the results, I would like to sort by:

  • the key fields first, sorted by position in the query results (since this usually reflects the unique index of the backend).

  • the rest of the keys are in alphabetical order, because the position reflects the tables of the physical order of the fields, which is not of interest.

Note. This is not what I want to do at the database level, it is a matter of Python sorting.

I can do it like this in Python 2.7 (see code below), but I want to prepare for Python 3.

I've written new operator.attrgetter / itemgetter types in the past, including sequential passes in which you sort one key function first, then another. But I don't see how the 3 key functional systems will handle branching.

#test data, mangled on purpose
data = [
    dict(fieldname="anotherkey2", pos=1, key=True),
    dict(fieldname="somekey1", pos=0, key=True),
    dict(fieldname="bfield3", pos=2, key=False),
    dict(fieldname="afield", pos=3, key=False),
    dict(fieldname="cfield", pos=4, key=False),
]

#exp keys, first, by position, then non-keys, alphabetic order
exp = ["somekey1","anotherkey2","afield","bfield3","cfield"]

def cmp2(field1, field2):

    key1, key2 = field1.get("key"), field2.get("key")

    #if both are keys, go by position in cursor results
    if key1 and key2:
        return cmp(field1["pos"], field2["pos"])

    #if neither are keys, order alphabetically
    if not (key1 or key2):
        return cmp(field1["fieldname"], field2["fieldname"])

    #otherwise, keys go first
    return cmp(key2, key1)

for func in [cmp2]:
    test_data = data[:]
    test_data.sort(cmp=func)
    got = [field["fieldname"] for field in test_data]
    try:
        msg = "fail with function:%s exp:%s:<>:%s:got" % (func.__name__, exp, got)
        assert exp == got, msg
        print ("success with %s: %s" % (func.__name__, got))
    except AssertionError,e:
        print(e)

      

Ouput:

success with cmp2: ['somekey1', 'anotherkey2', 'afield', 'bfield3', 'cfield']

      

In addition, the cmp_to_key recipe in the Sorting HOWTO looks scary and rather non-Python-language, with a lot of repetitive code for each magic function. And I'm not sure how important functools.cmp_to_key is .

I guess what I could do to pre-decorate the field dictionaries with an additional attribute that determines how to sort. Something like a tuple sortby = (not key, pos if key else 0, fieldname)

, but hoping for a cleaner approach.

It works, but ... nothing better?

def pre_compute(data):
    for row in data:
        key, pos, fieldname = row["key"], row["pos"], row["fieldname"]
        sortby = (not key, (pos if key else 0), fieldname)
        row["sortby"] = sortby

for func in [pre_compute]:
    test_data = data[:]

    func(test_data)

    test_data.sort(key=itemgetter('sortby'))

    got = [field["fieldname"] for field in test_data]
    try:
        msg = "fail with function:%s exp:%s:<>:%s:got" % (func.__name__, exp, got)
        assert exp == got, msg
        print ("success with %s: %s" % (func.__name__, got))
    except AssertionError,e:
        print(e)

      

+3


source to share


2 answers


cmp_to_key()

(either a stand-alone version or built into the functools module) turns an arbitrary function used with the sort parameter cmp=

into one that can be used with the new parameter key=

. This would be the simplest solution to your problem (although getting the database for this might be better, as some commenters have pointed out).



+2


source


Sort by whether the field is a key field or field or field name depending on whether it is a key field.



def keyfunc(field):
    return (not field['key'], field['pos'] if field['key'] else field['fieldname'])

      

+1


source







All Articles