Welcome, guest | Sign In | My Account | Store | Cart

Sorts a list of objects by either attribute or index across a prioritized group of attributes, indices, or both.

Python, 166 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class DeepSort(object):  # Python 2.2.x version
   """
   Sorts a list of objects  by either attribute or index  across a prioritized
   group of attributes, indices, or both.

   Usage:
      dSort = DeepSort()
      dSort(Alist [, val1] [..., valN])

   Where val may be an integer, a string, a tuple or list. When val is an int-
   eger,  it represents  the index of a  value in an object.  When  val  is  a
   string,  it represents an  attribute of an object. When val is a tuple or a
   list, it may contain only integers (indices) or strings  (attributes).   If
   needed, vals may be passed as a single tuple or list:
      dSort(Alist [, (val1, [..., valN])])

   Objects may be compared to an  arbitrary depth of  indices/attributes (e.g.
   sort(Alist,(0, 'a'),(1, 'a')) will sort each object according to its second
   level of attr / index (by obj[0]['a'] first, then by obj[1]['a'] needed)).
   """
   # If all comparisons but 1 fail with exceptions, a sort will still occur.

   def __init__(self):
      
      self.vals = None
      self.valTypes = (type(1), type('a'))
      self.seqTypes = (type((1,)), type([1]))
      self.hit, self.diff = 0, 0
      self.valStr = ''

   def __call__(self, seq, *args):
      
      if args in ((), (()), ([])):
         seq.sort()
         return None
      elif (len(args) == 1) and (type(args[0]) in self.seqTypes):
         self.vals = args[0]
      else:
         self.vals = args

      seq.sort(self.__comp__)
      
   def __comp__(self, this, that):

      self.hit, self.diff = 0, 0
      
      for val in self.vals:
         if type(val) in self.valTypes:
            # optimized for comparisons at level 1
            try:
               self.diff = cmp(this[val], that[val])
               self.hit = 1
            except:
               continue
            if self.diff:
               return self.diff
         elif type(val) in self.seqTypes:
            # generalized for comparisons at levels 2+
            self.valStr = ''
            for v in val:
               if type(v) == type(1):
                  self.valStr += "[%s]" % v
               elif type(v) == type('a'):
                  self.valStr += "[\'%s\']" % v
               else:
                  raise AttributeError, 'Nested arg must be int or string.'
            try:
               exec('self.diff = cmp(this%s, that%s)' % (self.valStr, self.valStr))
               self.hit = 1
            except:
               continue
            if self.diff:
               return self.diff
         else:
            raise AttributeError, 'Primary arg must be int, string, tuple, or list.'
         
      if self.hit:
         # all successful cmp() calls returned 0
         return 0
      else:
         # no successful cmp() calls
         raise ValueError, 'No args in common between compared objects.'

#
# test code
#

from random import randrange

dSort = DeepSort()

if __name__ == '__main__':

   tpls = [((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1))), \
      ((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1))), \
      ((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1))), \
      ((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1))), \
      ((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1))), \
      ((randrange(0, 5, 1), randrange(0, 5, 1)), \
      (randrange(0, 5, 1), randrange(0, 5, 1)))]
   
   dcts = [({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}), \
      ({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}), \
      ({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}), \
      ({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}), \
      ({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}), \
      ({'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)}, \
      {'a':randrange(0, 5, 1),'b':randrange(0, 5, 1)})]

   print
   print 'tpls:'
   print tpls
   print
   dSort(tpls)
   print 'dSort(tpls):'
   print tpls
   print
   dSort(tpls, 1, 0)
   print 'dSort(tpls, 1, 0):'
   print tpls
   print
   dSort(tpls, 0, 1)
   print 'dSort(tpls, 0, 1):'
   print tpls
   print
   dSort(tpls, (0, 1), (1, 0))
   print 'dSort(tpls, (0, 1), (1, 0)):'
   print tpls
   print
   dSort(tpls, [(1, 0), (0, 1)])
   print 'dSort(tpls, [(1, 0), (0, 1)]):'
   print tpls
   print
   print 'dcts:'
   print dcts
   print
   dSort(dcts)
   print 'dSort(dcts):'
   print dcts
   print
   dSort(dcts, 1, 0)
   print 'dSort(dcts, 1, 0):'
   print dcts
   print
   dSort(dcts, 0, 1)
   print 'dSort(dcts, 0, 1):'
   print dcts
   print
   dSort(dcts, (0, 'a'), (1, 'a'))
   print "dSort(dcts, (0, 'a'), (1, 'a')):"
   print dcts
   print
   dSort(dcts, ((1, 'a'), (0, 'a')))
   print "dSort(dcts, ((1, 'a'), (0, 'a'))):"
   print dcts
   print

I wanted a way to more deeply compare objects for purposes of sorting. I have extended a basic by-attribute compare class to accept a list of attributes and or indices against which comparisons should be made. The order of the attributes / indices in the list indicates their priority. That is, starting with the first attribute / index, each one is passed to cmp(), where the first call to return either a 1 or -1 determines the sort order of the compared objects.

If the objects being compared either do not share a listed attribute or do not have an object at the passed index, an exception is caught and ignored. If all comparisons throw exceptions, a ValueError is raised.

One of the conditions I wanted to take into account was a dSort attempt with a list of attributes / indices which may contain attributes / indices found only in derived classes made on a list of the parent class. Only one attribute list has to be designed, as any attributes / indices not in the parent class will be passed over during the comparisons.

Finally, if an empty set is passed to args, the default builtin sort() method is called.

Created by FMHj . on Thu, 18 Jul 2002 (PSF)
Python recipes (4591)
FMHj .'s recipes (2)

Required Modules

Other Information and Tasks