![](/img/trans.png)
[英]Naturally sort a list of alpha-numeric tuples by the tuple's first element in Python
[英]In Python, how can I naturally sort a list of alphanumeric strings such that alpha characters sort ahead of numeric characters?
这是我最近面临的一个有趣的小挑战。 我将在下面提供答案,但我很想知道是否有更优雅或更有效的解决方案。
呈现给我的需求描述:
这是一个测试数据集:
test_cases = [
# (unsorted list, sorted list)
(list('bca'), ['a', 'b', 'c']),
(list('CbA'), ['A', 'b', 'C']),
(list('r0B9a'), ['a', 'B', 'r', '0', '9']),
(['a2', '1a', '10a', 'a1', 'a100'], ['a1', 'a2', 'a100', '1a', '10a']),
(['GAM', 'alp2', 'ALP11', '1', 'alp100', 'alp10', '100', 'alp1', '2'],
['alp1', 'alp2', 'alp10', 'ALP11', 'alp100', 'GAM', '1', '2', '100']),
(list('ra0b9A'), ['A', 'a', 'b', 'r', '0', '9']),
(['Abc', 'abc', 'ABc'], ['ABc', 'Abc', 'abc']),
]
奖励测试用例
这是受詹妮·卡里拉 ( Janne Karila)在下面的评论的启发,该评论当前选定的答案失败了(但在我看来,这实际上并不是一个实际的问题):
(['0A', '00a', 'a', 'A', 'A0', '00A', '0', 'a0', '00', '0a'],
['A', 'a', 'A0', 'a0', '0', '00', '0A', '00A', '0a', '00a'])
re_natural = re.compile('[0-9]+|[^0-9]+')
def natural_key(s):
return [(1, int(c)) if c.isdigit() else (0, c.lower()) for c in re_natural.findall(s)] + [s]
for case in test_cases:
print case[1]
print sorted(case[0], key=natural_key)
['a', 'b', 'c']
['a', 'b', 'c']
['A', 'b', 'C']
['A', 'b', 'C']
['a', 'B', 'r', '0', '9']
['a', 'B', 'r', '0', '9']
['a1', 'a2', 'a100', '1a', '10a']
['a1', 'a2', 'a100', '1a', '10a']
['alp1', 'alp2', 'alp10', 'ALP11', 'alp100', 'GAM', '1', '2', '100']
['alp1', 'alp2', 'alp10', 'ALP11', 'alp100', 'GAM', '1', '2', '100']
['A', 'a', 'b', 'r', '0', '9']
['A', 'a', 'b', 'r', '0', '9']
['ABc', 'Abc', 'abc']
['ABc', 'Abc', 'abc']
编辑:我决定重温这个问题,看看是否有可能处理奖金案。 它要求在钥匙的平局部分更为复杂。 为了匹配期望的结果,必须在数字部分之前考虑键的字母部分。 我还在键的自然部分和决胜局之间添加了一个标记,以使短键始终位于长键之前。
def natural_key2(s):
parts = re_natural.findall(s)
natural = [(1, int(c)) if c.isdigit() else (0, c.lower()) for c in parts]
ties_alpha = [c for c in parts if not c.isdigit()]
ties_numeric = [c for c in parts if c.isdigit()]
return natural + [(-1,)] + ties_alpha + ties_numeric
这将为上述测试用例生成相同的结果,再加上奖励用例所需的输出:
['A', 'a', 'A0', 'a0', '0', '00', '0A', '00A', '0a', '00a']
这也是适用于红利测试的一种:
def mykey(s):
lst = re.findall(r'(\d+)|(\D+)', s)
return [(0,a.lower()) if a else (1,int(n)) for n, a in lst]\
+ [a for n, a in lst if a]\
+ [len(n) for n, a in lst if n]
def mysort(lst):
return sorted(lst, key=mykey)
使用这种类型的模式,re.findall将字符串拆分为一个元组列表,例如
>>> re.findall(r'(\d+)|(\D+)', 'ab12cd')
[('', 'ab'), ('12', ''), ('', 'cd')]
此功能目前不要求任何性能:
def alpha_before_numeric_natural_sensitive(unsorted_list):
"""presorting the list should work because python stable sorts; see:
http://wiki.python.org/moin/HowTo/Sorting/#Sort_Stability_and_Complex_Sorts"""
presorted_list = sorted(unsorted_list)
return alpha_before_numeric_natural(presorted_list)
def alpha_before_numeric_natural(unsorted_list):
"""splice each string into tuple like so:
'abc100def' -> ('a', 'b', 'c', 100, 'd', 'e', 'f') ->
(ord('a'), ord('b'), ord('c'), ord('z') + 1 + 100, ...) then compare
each tuple"""
re_p = "([0-9]+|[A-za-z])"
ordify = lambda s: ord('z') + 1 + int(s) if s.isdigit() else ord(s.lower())
str_to_ord_tuple = lambda key: [ordify(c) for c in re.split(re_p, key) if c]
return sorted(unsorted_list, key=str_to_ord_tuple)
它基于此自然排序解决方案提供的见解以及我编写的此函数:
def alpha_before_numeric(unsorted_list):
ord_shift = lambda c: c.isdigit() and chr(ord('z') + int(c.lower()) + 1) or c.lower()
adjust_word = lambda word: "".join([ord_shift(c) for c in list(word)])
def cmp_(a, b):
return cmp(adjust_word(a), adjust_word(b))
return sorted(unsorted_list, cmp_)
有关比较不同功能的完整测试脚本,请参见http://klenwell.com/is/Pastebin20120829
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.