在Python中,我如何自然地对字母数字字符串列表进行排序,使字母字符排在数字字符之前?

kle*_*ell 1 python sorting

这是我最近遇到的一个有趣的小挑战.我将在下面提供我的答案,但我很想知道是否有更优雅或有效的解决方案.

描述了向我提出的要求:

  1. 字符串是字母数字(参见下面的测试数据集)
  2. 字符串应自然排序(请参阅此问题以获得解释)
  3. 字母字符应该在数字字符之前排序(即'100'之前的'abc')
  4. alpha字符的大写实例应该在小写实例之前排序(即'ABc','Abc','abc')

这是一个测试数据集:

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']),
]
Run Code Online (Sandbox Code Playgroud)

奖金测试案例

这是受Janne Karila在下面的评论的启发,目前选定的答案都失败了(但在我的案例中并不是真正的实际问题):

(['0A', '00a', 'a', 'A', 'A0', '00A', '0', 'a0', '00', '0a'],
        ['A', 'a', 'A0', 'a0', '0', '00', '0A', '00A', '0a', '00a'])
Run Code Online (Sandbox Code Playgroud)

Mar*_*som 6

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']
Run Code Online (Sandbox Code Playgroud)

编辑:我决定重新审视这个问题,看看是否有可能处理奖金案件.它需要在钥匙的断路器部分更复杂.要匹配所需的结果,必须在数字部分之前考虑键的alpha部分.我还在钥匙的自然部分和打破平局之间添加了一个标记,这样短按键总是在长按键之前.

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
Run Code Online (Sandbox Code Playgroud)

这会为上面的测试用例生成相同的结果,加上奖金案例的所需输出:

['A', 'a', 'A0', 'a0', '0', '00', '0A', '00A', '0a', '00a']
Run Code Online (Sandbox Code Playgroud)