任务
你想将一个字符串列表进行排序,这些字符串都含有数字的子串(比如一系列邮寄地址)。举个例子,“foo2.txt”应该出现在“foo10.txt”之前。然而,Python 默认的字符串比较是基于字母顺序的,所以默认情况下,“foo10.txt”会在“foo2.txt”之前。
解决方案
需要先将每个字符串切割开,形成数字和非数字的序列,然后将将每个序列中的数字转化成一个数。这会产生一个数的列表,可以用来做排序时比较的键,可以对这个排序应用 DSU——写两个函数即可,做起来很快捷:
import re
re_digits = re.compile(r'(\d+)')
def embedded_numbers(s):pieces = re_digits.split(s)#切成数字与非数字pieces[1::2] = map(int,pieces[1::2])#将数字部分转成整数return pieces
def sort_strings_with_embedded_numbers(alist):aux = [ (embedded_numbers(s),s) for s in alist ]aux.sort()return [ s for __, s in aux ]#惯例:__意味着“忽略”
在 Python 2.4 中,用相同的 embedded_number 函数,加上DSU的原生支持,代码变成
def sort_strings_with_embedded_numbers(alist):return sorted(alist,key=embedded_numbers)
讨论
假设有一个未排序的文件名的列表,比如:
files = 'file3.txt filell.txt file7.txt file4.txt filel5.txt'.split()
如果只是排序并打印列表,比如在 Python 2.4中用 print’ '.join(sorted(files))这样的代码,你的输出会是这样:file11.txt file15.txt file3.txt file4.txt file7.txt,因为默认情况下,字符串是根据字母顺序排序的(或者换句话说,排序顺序是由条目顺序指定的)。Python猜不到你的真实意图其实是希望让它以另外的方式处理那些含有数字的子串,所以必须准确地告诉 Python 你想要什么,解决方案中的代码主要所做的工作其实就是这么一件事。
基于解决方案的代码,也能获得一个更好看的结果:
print''.join(sort_strings_with_embedded_numbers(files))
现在输出变成了 file3.txt file4.txt file7.txt file11.txt file15.txt,这应该正好就是需要的顺序。
这个实现基于 DSU。如果想在 Python 2.3 中达到同样目的,需要手工制作 DSU,但如果你的代码只需要在 Python 2.4 中运行,直接使用原生内建的 DSU 即可。我们传递了一个叫做 key 的参数(一个函数,该函数对每个元素都会调用一次以获取正确的比较键来用于排序)给新的内建函数 sorted。
本节解决方案中的embedded_numbers函数正是用来为每个元素获取正确的比较键的方法:一个非数字子串交替出现的列表,int获取了每个数字子串。re_digits.split(s)给了我们一个交替出现的数字子串和非数字子串的列表(数字子串拥有偶数索引号),然后我们使用了内建的 map 和 int(采用了扩展切片的方式获得并设置了偶数索引号的元素)来将数字序列转化成整数。现在,对这个混合类型的列表进行的条目顺序比较就可以产生正确的结果了。