I l@ve RuBoard |
2.8 Sorting by Item or by AttributeCredit: Matthew Wood 2.8.1 ProblemYou need to sort a list of (x, y) coordinates by item y, or, more generally, sort a list of objects by any attribute or item. 2.8.2 SolutionYou might first think of something like the following class, based on the simple but slow approach of passing a comparison function to the sort method: class Sorter: # Notice how _ _compare is dependent on self._ _whichindex def _ _compare(self, x, y): return cmp(x[self._ _whichindex], y[self._ _whichindex]) # Pass the sort function the _ _compare function defined above def _ _call_ _(self, data, whichindex = None): if whichindex is None : data.sort( ) else : self._ _whichindex = whichindex data.sort(self._ _compare) return data # handy for inlining, and low-cost The trick is to use a bound method that accesses instance state as the comparison function to determine which item (or attribute, but this version deals only with items) to fetch. Unfortunately, this makes the approach nonreentrant and not thread-safe. Thanks to the faster, more robust, and more flexible DSU idiom, it's not hard to make a more general version that allows attributes as well as items, guarantees a stable sort, is both reentrant and thread-safe, and is speedier to boot: class Sorter: def _helper(self, data, aux, inplace): aux.sort( ) result = [data[i] for junk, i in aux] if inplace: data[:] = result return result def byItem(self, data, itemindex=None, inplace=1): if itemindex is None: if inplace: data.sort( ) result = data else: result = data[:] result.sort( ) return result else: aux = [(d[i][itemindex], i) for i in range(len(data))] return self._helper(data, aux, inplace) # a couple of handy synonyms sort = byItem _ _call_ _ = byItem def byAttribute(self, data, attributename, inplace=1): aux = [(getattr(d[i],attributename),i) for i in range(len(data))] return self._helper(data, aux, inplace) Of course, since the second version doesn't use its "classhood" in any way, making it a class is somewhat peculiar. It would be far more Pythonic, and clearer, to use a module with free-standing functions, rather than an artificial class with instances that have no state. 2.8.3 DiscussionHow do you efficiently sort a list of (x, y) coordinates by y? More generally, how do you sort a list of dictionaries, lists, or class instances by a particular item or attribute? I hate not being able to sort by any item or attribute, so I wrote an auxiliary class to do it for me. The DSU idiom is much faster than passing sort a comparison function, as discussed in other recipes. The second version of Sorter in this recipe uses DSU because of this, as well as auxiliary flexibility advantages. This second version gets no benefit from being a class rather than just a couple of functions, but casting it as a class makes it drop-in compatible with the first, inferior version, which did use some state as a trick (losing reentrancy and thread-safety in the process). Here is some example code (note that it instantiates the Sorter class only once, another hint that it is not at all an optimal architecture to wrap this functionality as a class): sort = Sorter( ) if _ _name_ _ == '_ _main_ _' : list = [(1, 2), (4, 8), (0, 3)] dict = [{'a': 3, 'b': 4}, {'a': 5, 'b': 2}, {'a': 0, 'b': 0}, {'a': 9, 'b': 9}] dumb = [1, 4, 6, 7, 2, 5, 9, 2, 4, 6] print 'list normal:', list sort(list, 0) print 'sort by [0]:', list sort(list, 1) print 'sort by [1]:', list print print "dict normal:", dict sort(dict, 'a') print "sort by 'a':", dict sort(dict, 'b') print "sort by 'b':", dict print print 'dumb normal:', dumb sort(dumb) print 'normal sort:', dumb Returning the sorted list is cheap (it's just an extra reference, since Python, fortunately, never does any copying of data unless you specifically request it) and offers uniform behavior between in-place and non-in-place cases. Often, we only want to do: for x in sort(something, inplace=0): Returning a reference to the sorted data gives us just the right amount of flexibility for this. 2.8.4 See AlsoRecipe 2.4 and Recipe 4.10. |
I l@ve RuBoard |