如何对不同类型的列表进行排序?

Iwa*_*993 2 python sorting python-3.x

我需要使用 python 3 对列表进行排序。可能有stringsintegersfloats、 或tuples等。

我目前正在尝试sort使用如下key参数正确使用该函数:

data.sort(key=gen_key)

...

def gen_key(self, value):
        if is_number(value):
            return str(value)

        if isinstance(value, str):
            return value
    return '___' + type(value).__name__
Run Code Online (Sandbox Code Playgroud)

但问题是数字现在将自然排序。虽然我想对数字和浮点数进行排序,但仍然像数字和浮点数一样,而不是将它们视为字符串。

该行为是由return str(value)零件引起的。但是我不能返回与字符串不同的类型,因为这会引发异常,因为从 python 3 开始,字符串不会像在 python 2 中那样用数字排序。异常如下

unordarable types: int() < str()
Run Code Online (Sandbox Code Playgroud)

有什么建议?

Sha*_*ger 5

诀窍是让您的key函数返回一个元组,在第一个索引中具有保证的可比较类型,并在后续索引中返回不同的类型。

虽然与 Python 2 的功能并非 100% 相同,但对于“数字在前面,其他所有内容按类型名进行比较”的特定情况,您可以使用一个相当有效的key函数来做到这一点:

>>> from numbers import Number
>>> seq = ['Z', 3, 'Y', 1, 'X', 2.5, False, (1, 2), [2, 3], None]
>>> sorted(seq, key=lambda x: (x is not None, '' if isinstance(x, Number) else type(x).__name__, x))
[None, False, 1, 2.5, 3, [2, 3], 'X', 'Y', 'Z', (1, 2)]
Run Code Online (Sandbox Code Playgroud)

key这里的函数使 a 的第一个元素变得key简单bool,强制None在其他所有元素之前进行排序(Py2 做了同样的事情),然后首先使用空字符串作为键的第二部分对所有数字类型进行排序,其他所有元素都使用它们的类型名称(也像 Py2)。一旦你通过了前两个索引,剩下的就是相同的类型,应该比较好。

这里的主要缺陷是,可比的非数字类型,如setfrozenset不会相互比较,他们会通过类型名称只(自定义键类使用异常可以处理此)进行排序。

它也不会处理递归情况;如果序列包含[2, 3]and ['a', 'b'],它将与TypeError进行比较,但除了一个荒谬的关键类之外,没有什么可以处理的。2'a'

如果这不是问题,那么运行起来很便宜并且相对简单。

与涉及__lt__定义来执行比较的自定义类的解决方案不同,这种方法具有生成内置键的优势,这与排序期间 Python 级代码的最少执行有效地进行比较。

时间:

 # Multiply out the sequence so log n factor in n log n work counts for something
 >>> seq = ['Z', 3, 'Y', 1, 'X', 2.5, False, (1, 2), [2, 3], None] * 100

 # Verify equivalence
 >>> sorted(seq, key=Py2Key) == sorted(seq, key=lambda x: (x is not None, '' if isinstance(x, Number) else type(x).__name__, x))
 True

 # Timings in seconds for the fastest time (of 3 trials) to run the sort 1000 times:
 >>> import timeit

 # Py2Key class
 >>> min(timeit.repeat('sorted(seq, key=Py2Key)', 'from __main__ import seq, Py2Key', number=1000))
 5.251885865057375

 >>> min(timeit.repeat('sorted(seq, key=lambda x: (x is not None, "" if isinstance(x, Number) else type(x).__name__, x))', 'from __main__ import seq, Number', number=1000))
 1.9556877178131344
Run Code Online (Sandbox Code Playgroud)

基本上,避免动态 Python 级别的开销__lt__将运行时间减少了 60% 以上。这似乎不是算法上的改进(seq100 倍长具有相同的运行时间比率),只是固定开销的减少,但这是一个非平凡的减少。