Python - 使用__lt__运算符在pyQt中对数字进行人性化排序
我有一些数据行,想把它们展示成下面这个样子:
1
1a
1a2
2
3
9
9.9
10
10a
11
100
100ab
ab
aB
AB
因为我在使用pyQt,并且代码是在一个TreeWidgetItem里面,所以我现在要解决的代码是:
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
4 个回答
这里有一个函数,它可以处理一个包含字母和数字的字符串,并返回一个元组,这个元组可以按照“自然”的方式进行排序。
def naturalkey(key, convert=int):
if not key:
return ()
keys = []
start = 0
extra = ""
in_num = key[0].isdigit()
for i, char in enumerate(key):
if start < i:
if in_num:
try:
last_num = convert(key[start:i])
except:
in_num = False
if i > 2 and key[i-2] == ".":
extra = "."
keys.append(last_num)
start = i-1
if not in_num: # this is NOT equivalent to `else`!
if char.isdigit():
keys.append(extra + key[start:i])
in_num = True
start = i
extra = ""
last_num = convert(char)
keys.append(last_num if in_num else (extra + key[start:]))
return tuple(keys)
这个函数的基本思路是,当它遇到数字时,会收集后面的字符,并不断尝试把这些字符转换成数字,直到无法再转换为止(也就是说,遇到错误)。默认情况下,它会把字符序列转换成整数,但你可以传入 convert=float
让它也能处理小数点。(不幸的是,它不支持科学计数法,比如 '1e3',因为它会先尝试解析 '1e',这会出错。虽然可以特别处理这种情况,但看起来在你的使用场景中并不必要。)
这个函数返回的元组包含了字符串和数字,顺序和它们在原字符串中出现的顺序一致,数字会被转换成你指定的类型。例如:
naturalkey("foobar2000.exe")
>>> ("foobar", 2000, ".exe")
这个元组可以用作排序字符串列表的关键字:
my_list.sort(key=lambda i: naturalkey(i, float))
或者你可以用它来实现一个比较函数:
def __lt__(self, other):
return naturalkey(self.value, float) < naturalkey(other.value, float)
更好的做法是,在对象的 __init__()
方法中生成自然排序的关键字,把它存储在实例中,然后让你的比较函数使用这个存储的值。如果这个关键字的来源值是可变的,你可以写一个属性,当底层值更新时更新这个关键字。
使用samplebias的swapcase
想法,以及Ned Batchelder的人性化排序代码,你可以这样做:
import re
def human_keys(astr):
'''
alist.sort(key=human_keys) sorts in human order
'''
keys=[]
for elt in re.split('(\d+)', astr):
elt=elt.swapcase()
try: elt=int(elt)
except ValueError: pass
keys.append(elt)
return keys
x='''
1
1a
1a2
2
3
9
9.9
9.10
9a2
10
10a
11
100
100ab
ab
aB
AB
'''.split()
print(x)
assert x == sorted(x,key=human_keys)
你可以在__lt__
中应用human_keys
,像这样:
def __lt__(self, otherItem):
column = self.treeWidget().sortColumn()
orig = str(self.text(column).toLower()).rjust(20, "0")
other = str(otherItem.text(column).toLower()).rjust(20, "0")
return human_keys(orig) < human_keys(other)
这可能对你有帮助。你可以编辑正则表达式,让它匹配你感兴趣的数字模式。我的设置会把包含.
的数字字段当作浮点数来处理。使用swapcase()
函数可以把字母的大小写反转,这样'A'
就会排在'a'
后面。
更新: 精炼内容:
import re
def _human_key(key):
parts = re.split('(\d*\.\d+|\d+)', key)
return tuple((e.swapcase() if i % 2 == 0 else float(e))
for i, e in enumerate(parts))
nums = ['9', 'aB', '1a2', '11', 'ab', '10', '2', '100ab', 'AB', '10a',
'1', '1a', '100', '9.9', '3']
nums.sort(key=_human_key)
print '\n'.join(nums)
输出结果:
1
1a
1a2
2
3
9
9.9
10
10a
11
100
100ab
ab
aB
AB
更新: (对评论的回应)如果你有一个类Foo
,并想用_human_key
排序方案来实现__lt__
,只需返回_human_key(k1) < _human_key(k2)
的结果即可;
class Foo(object):
def __init__(self, key):
self.key = key
def __lt__(self, obj):
return _human_key(self.key) < _human_key(obj.key)
>>> Foo('ab') < Foo('AB')
True
>>> Foo('AB') < Foo('AB')
False
所以在你的情况下,你可以这样做:
def __lt__(self, other):
column = self.treeWidget().sortColumn()
k1 = self.text(column)
k2 = other.text(column)
return _human_key(k1) < _human_key(k2)
其他比较运算符(__eq__
、__gt__
等)也可以用同样的方法来实现。