我需要使用python 3对列表进行排序。可能有strings
或integers
floats
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__
但问题是数字现在会自然排序。虽然我想订购数字和浮点数仍然像数字和浮点数,而不是将它们作为字符串威胁。
行为是由return str(value)
部分引起的。但我不能返回一个不同于字符串的类型,因为这将引发异常,因为python 3字符串不会像在python 2中那样用数字排序。例外是以下
unordarable types: int() < str()
有什么建议吗?
答案 0 :(得分:2)
最简洁的方法是使用比较方法中包含所需排序行为的对象作为排序键。 Python排序所需的唯一比较方法是__lt__()
,因此这相当简单。
例如,这是一个大致实现Python 2排序启发式的类(在可比较的对象组中的值中排序)。您当然可以实施您喜欢的任何其他规则。由于排序将为列表中的每个项目创建其中一个对象,因此我使用__slots__
和实习所有类型字符串,尽可能降低每个对象的大小。
from sys import intern
class Py2Key:
__slots__ = ("value", "typestr")
def __init__(self, value):
self.value = value
self.typestr = intern(type(value).__name__)
def __lt__(self, other):
try:
return self.value < other.value
except TypeError:
return self.typestr < other.typestr
用法:
seq = ["Z", 3, "Y", 1, "X", 2.5, False]
sorted(seq, key=Py2Key)
>>> [False, 1, 2.5, 3, 'X', 'Y', 'Z']
不幸的是,在Python 3中实现Python 2的排序行为比Python 2更慢,内存更密集,特别是因为我们正在利用异常处理。这在您的应用程序中是否可接受取决于您。
答案 1 :(得分:2)
诀窍是让你的key
函数返回第一个索引中具有保证可比类型的元组,以及后续索引中的不同类型。
虽然与Python 2没有100%完全相同,但对于前面的#34;数字的特定情况,其他所有内容都通过typename&#34;进行比较。你可以用一个相当有效的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)]
这里的key
函数使key
的第一个元素变为简单bool
,迫使None
在其他所有内容之前排序(Py2做同样的事情),然后首先使用空字符串为键的第二部分排序所有数字类型,其中所有其他使用其类型名称(也像Py2)。一旦你超过前两个指数,剩下的是同一类型,应该比较好。
这里的主要缺陷是可比的非数字类型(例如set
和frozenset
)不会相互比较,它们只能按类型名称排序(自定义键)使用异常的类可以处理这个问题。)
它也不会处理递归案件;如果序列包含[2, 3]
和['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
基本上,避免动态Python级__lt__
的开销会使运行时减少60%以上。它似乎不是一个算法改进(一个seq
长100倍具有相同的运行时间比率),只是固定开销的减少,但它是一个非平凡的减少。