This question already has an answer here:
I have data rows and wish to have them presented as follows:
1
1a
1a2
2
3
9
9.9
10
10a
11
100
100ab
ab
aB
AB
As I am using pyQt and code is contained within a TreeWidgetItem, the code I'm trying to solve is:
def __lt__(self, otherItem):
column = self.treeWidget().sortColumn()
#return self.text(column).toLower() < otherItem.text(column).toLower()
orig = str(self.text(column).toLower()).rjust(20, "0")
other = str(otherItem.text(column).toLower()).rjust(20, "0")
return orig < other
Using samplebias's
swapcase
idea, and Ned Batchelder's human-sort code, you might do it this way:You could apply
human_keys
in__lt__
like this:I don't understand your sort algorithm, so I can't tell you how to implement it. But there is a general technique, which is to use the
key
parameter in Python's builtinsort
function. In other words, you want to come up with some transformation of your data which Python would sort in the correct order, and then write that transformation as a Python functionfoo
and callsort(data, key=foo)
.Example: if you had a list of strings
"<integer>-<integer>"
, say["1-1","1-2","3-1"]
and you wanted to sort by the second number and then the first, notice that Python would sort the data correctly if it were in the form[(1,1), (2,1), (1,3)]
i.e. a list of reversed tuples. So you would write a functionand then sort the list with
sort(l, key=key)
.Here's a function that, given a string with a mixture of alphabetical and numeric parts, returns a tuple that will sort in a "natural" way.
The basic approach it uses is, when it sees a digit, it gathers additional characters and keeps trying to convert the result to a number until it can't anymore (i.e. it gets an exception). By default it tries to convert runs of characters to an integer, but you can pass in
convert=float
to have it accept decimal points. (It won't accept scientific notation, unfortunately, since to get something like '1e3' it would first try to parse '1e' which is invalid. This, along with the + or - sign, could be special-cased but it doesn't look like that is necessary for your use case.)The function returns a tuple containing strings and numbers in the order they were found in the string, with the numbers parsed to the specified numeric type. For example:
This tuple can be used as a key for sorting a list of strings:
Or you can use it to implement a comparison function:
It would be better (faster) to generate the natural key in the object's
__init__()
method, store it in the instance, and write your comparison function(s) to use the stored value instead. If the value from which the key is derived is mutable, you could write a property that updates the key when the underlying value is updated.This may help you. Edit the regexp to match the digit patterns you're interested in. Mine will treat any digit fields containing
.
as floats. Usesswapcase()
to invert your case so that'A'
sorts after'a'
.Updated: Refined:
Output:
Update: (response to comment) If you have a class
Foo
and want to implement__lt__
using the_human_key
sorting scheme, just return the result of_human_key(k1) < _human_key(k2)
;So for your case, you'd do something like this:
The other comparison operators (
__eq__
,__gt__
, etc) would be implemented in the same way.