在Python中,如何自然地对字母数字字符串列表进行排序,使字母字符优先于数字字符排序?

在Python中,如何自然地对字母数字字符串列表进行排序,使字母字符优先于数字字符排序?,python,sorting,Python,Sorting,这是我最近遇到的一个有趣的小挑战。我将在下面提供我的答案,但我很好奇是否有更优雅或有效的解决方案 向我提出的要求的描述: 字符串是字母数字的(参见下面的测试数据集) 字符串应自然排序(有关说明,请参阅) 字母字符应排在数字字符之前(即“100”之前的“abc”) 字母字符的大写实例应排在小写实例之前(即“ABc”、“ABc”、“ABc”) 下面是一个测试数据集: test_cases = [ # (unsorted list, sorted list) (list('bca'),

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

向我提出的要求的描述:

  • 字符串是字母数字的(参见下面的测试数据集)
  • 字符串应自然排序(有关说明,请参阅)
  • 字母字符应排在数字字符之前(即“100”之前的“abc”)
  • 字母字符的大写实例应排在小写实例之前(即“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']),
    ]
    

    奖金测试案例

    这是受以下所选答案目前失败的启发(但在我的案例中,这并不是一个实际问题):


    该职能部门目前未就绩效做出任何声明:

    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_)
    

    有关此处比较不同功能的完整测试脚本,请参见

    此功能此时不声明性能:

    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_)
    
    有关此处比较不同函数的完整测试脚本,请参阅

    编辑:我决定重新考虑这个问题,看看是否有可能处理奖金问题。这需要在钥匙的断开领带部分更加复杂。为了匹配所需的结果,必须先考虑键的字母部分,然后再考虑数字部分。我还添加了一个标记之间的自然部分的关键和领带断路器,使短的关键总是来之前,长的

    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 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 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')]
    

    像这样的挑战,特别是那些已经有了解决方案的挑战,通常属于代码评审或代码GolfJanne:Corrected title。抢手货谢谢大卫:什么是代码审查或代码高尔夫?如果这是真的,那么在问题中添加“回答你自己的问题”框有什么意义呢?@KeithRandall,根据自然排序规则,应该考虑整数,而不仅仅是单个数字-因此
    a2
    应该排在
    a100
    之前。至于大小写顺序,我认为顺序应该是
    AaBbCcDd…
    。哪个应该放在第一位:
    00A
    还是
    0a
    ?@JanneKarila,我没有“官方”规范,但我会告诉你我的答案是如何工作的。
    00
    0
    比较相等,
    A
    A
    比较相等。因此,这就落在了平局破坏者的头上,他们正在以二进制方式比较原始字符串,
    00
    出现在
    0a
    之前。像这样的挑战,特别是那些已经有了解决方案的挑战,通常属于代码审查或代码GolfJanne:更正的标题。抢手货谢谢大卫:什么是代码审查或代码高尔夫?如果这是真的,那么在问题中添加“回答你自己的问题”框有什么意义呢?@KeithRandall,根据自然排序规则,应该考虑整数,而不仅仅是单个数字-因此
    a2
    应该排在
    a100
    之前。至于大小写顺序,我认为顺序应该是
    AaBbCcDd…
    。哪个应该放在第一位:
    00A
    还是
    0a
    ?@JanneKarila,我没有“官方”规范,但我会告诉你我的答案是如何工作的。
    00
    0
    比较相等,
    A
    A
    比较相等。因此,这就落在了打破僵局的人身上,他们以二进制方式比较原始字符串,
    00
    出现在
    0a
    之前。我喜欢您使用正则表达式简化理解条件的方式。P.S。这依赖于Python在元组和非元组之间的排序保证,这在Python 3中根本不起作用。你可以用和我一样的方法来修复它,在平局断路器前放一个虚拟标记。我喜欢你用正则表达式简化理解条件的方式。这依赖于Python在元组和非元组之间的排序保证,这在Python 3中根本不起作用。你可以用和我一样的方法来修复它,在平局断路器前放一个虚拟标记。看见