Sorts a list of objects by either attribute or index across a prioritized group of attributes, indices, or both.
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.