【问题标题】:How to sort a list of different types?如何对不同类型的列表进行排序?
【发布时间】:2016-04-17 20:20:09
【问题描述】:

我需要使用python 3对列表进行排序。可能有stringsintegersfloatstuples等。

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

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

有什么建议吗?

【问题讨论】:

  • 您期待什么结果?您希望如何对字符串和元组进行排序?
  • 你会如何想要 'A'13 进行排序?您需要提出一个明确定义的排序顺序。一旦你这样做了,你就已经完成了。

标签: python python-3.x sorting


【解决方案1】:

诀窍是让您的 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)]

这里的key 函数使key 的第一个元素成为简单的bool,强制None 在其他所有内容之前排序(Py2 做了同样的事情),然后使用键的第二部分的空字符串,其他所有内容都使用它们的类型名称(也像 Py2)。一旦您通过了前两个索引,剩下的就是相同的类型,并且应该可以比较。

这里的主要缺陷是类似的非数字类型,如setfrozenset 不会相互比较,它们将仅按类型名排序(使用异常的自定义键类可以处理这个问题)。

它也不会处理递归情况;如果序列包含[2, 3]['a', 'b'],它将有一个TypeError2'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 倍的运行时间比相同),只是减少了固定开销,但这是一个不平凡的减少。

【讨论】:

  • 您能否使用用户定义的键扩展该方法。例如,iterable 可能包含用户想要在特定字典键key=lambda x: x['mykey'] 上排序的字典,并且如上所述,与x[mykey] 关联的值可能是混合类型。我想知道您的 lambda 是否可以后组合。会试试的。
  • 似乎还可以:seqd = [{'a':x} for x in seq]。然后sorted(seqd,key = lambda x: type_markup(x['a'])) 其中type_markup 是你的lambda。
【解决方案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 更慢且更占用内存,尤其是因为我们正在利用异常处理。这在您的应用程序中是否可接受取决于您。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-23
    • 1970-01-01
    • 2011-02-10
    • 2021-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    相关资源
    最近更新 更多