2016-01-13 27 views
1

我需要使用Python 3到一个列表排序,有可能是stringsintegersfloatstuplesPython的排序不同类型的列表

我目前正在使用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)部分引起的。但我不能返回不同的类型的字符串,因为这将引发异常,如蟒蛇3串不会用数字排序的,就像他们在蟒蛇2.做的异常以下

unordarable types: int() < str() 

有什么建议?

+2

你期待什么结果?你期望如何排序字符串和元组? – jprockbelly

+1

你会怎么样?''''与'13'排序?您需要提出明确的排序顺序。一旦你完成了,你几乎已经完成了。 –

回答

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 2的在Python 3排序行为将是缓慢和内存密集型比Python 2,特别是因为我们正在采取例外的优势处理。您的应用程序是否可以接受取决于您。

2

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

虽然不是100%相同的都是Python 2呢,为你的具体情况“号码前,一切由类型名称相比,”你可以用一个合理有效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'],它将有一个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倍),只是减少了固定开销,但这是一个不小的缩减。