9

Background:

I would like to know how I can implement advanced sorting functions that I can pass in as tuple element to the key argument of the python 'sorted' function.

Here is an example depicting what I would like to do:

class Book:

      def __init__(self, name, author, language, cost):
          self.name = name
          self.author = author
          self.language=language
          self.cost = cost


bookList = [list of books]

firstLanguage = "Armenian"
possibleLanguages = ["English", "Spanish", "Armenian", "French", "Chinese", "Swahili"]
possibleLanguages.remove("Armenian")

sortedBookList = sorted(bookList, key=(sortByName,
    sortByFirstLanguage(firstLanguage), sortByLanguages(possibleLanguages) ))

Basically I would like to implement the 'sortByFirstLanguage' and 'sortByLanguages' functions described above so that I can pass them to the python 'sorted' function as the tuple items of the 'key' argument. Here is some example code regarding what the custom sort functions should look like:

def sortByName(elem):
    return elem.name

def sortByFirstLanguage(elem, firstLanguage):
    if elem.language == firstLanguage:
       return 1
    else:
       return -1


def sortByLanguages(elem, possibleLanguages):
    if elem.language in possibleLanguages:
       return possibleLanguages.index(elem.language)

Addt. Details:

  1. I am using python 2.7
  2. This problem is actually using Django querysets rather than lists of objects, but for demonstration of purpose, I think a list of objects serves the same purpose.
  3. The goal of this sorting is to sort by a specified language first, then go back && sort the remaining items by their default ordering (list ordering in this case).

Question:

How exactly can I tell the 'key' argument to pass in the extra arguments 'firstLanguage' && 'possibleLanguages' to custom sorting functions as I have shown above?

smci
  • 32,567
  • 20
  • 113
  • 146
Buck
  • 731
  • 1
  • 11
  • 23
  • `lambda elem: sortByFirstLanguage(elem, firstLanguage)`? That will take the `elem` from the element where `sorted()` calls it, and `firstLanguage` from where it is defined. – Sam Mussmann Oct 27 '13 at 20:03
  • You have no hope of being able to pass them in that particular way, because that's not how `key` works. `key` has to take a function (or any callable) that is called on each item and returns the sort key. You may be able to achieve a similar effect with a slightly different design, though. – BrenBarn Oct 27 '13 at 20:05
  • 2
    what is wrong with ordering your queryset using djangos built in methods? What do you need `sortedBookList` to contain? A list of books ordered by language with a certain language listed first? – dm03514 Oct 27 '13 at 20:07
  • The `key` parameter accepts only 1 function, so you'll have to write a function that does all 3 types of sorting. – shad0w_wa1k3r Oct 27 '13 at 20:11

1 Answers1

7

As Ashish points out in the comments, we first need to combine these functions, since key only accepts a single functions. If we return a sequence (list, tuple) of the function results, Python will do the right thing, only comparing later (farther right) elements if the earlier elements are equal (source).

I know of a couple ways to do this.

Using lambdas:

sortedBookList = sorted(
    bookList, 
    key=lambda elem: (sortByName(elem), 
                      sortByFirstLanguage(elem, firstLanguage), 
                      sortByLanguages(elem, possibleLanguages)))

Using higher-order functions:

def key_combiner(*keyfuncs):
  def helper(elem):
    return [keyfunc(elem) for keyfunc in keyfuncs]
  return helper

def sortByFirstLanguage(firstLanguage):
  def helper(elem):
    return elem.language == firstLanguage  # True > False
  return helper

def sortByLanguages(possibleLanguages):
  def helper(elem):
    if elem.language in possibleLanguages:
       return possibleLanguages.index(elem.language)
  return helper

sortedBookList = sorted(bookList,
                        key=key_combiner(sortByName, 
                                         sortByFirstLanguage(firstLanguage), 
                                         sortByLanguages(possibleLanguages))

Lambdas seem cleanest to me, so that's probably what I'd use.

Community
  • 1
  • 1
Sam Mussmann
  • 5,883
  • 2
  • 29
  • 43