【问题标题】:Ignore unorderable data type while sorting tuple对元组进行排序时忽略不可排序的数据类型
【发布时间】:2014-04-14 13:48:51
【问题描述】:

我正在从一本书中自学 Python,但我一直在进行编程练习。练习的目的是制作一个对象列表,然后根据某些对象属性对这些对象进行排序。

我的教科书的作者说,在排序期间使用一个键为每次比较调用一个类方法会减慢大型数据集的排序时间,并且通过为每个对象调用一次类方法来构建一个“装饰”列表你建立的列表可以使后续的排序更有效率。编程问题规定,我应该创建一系列元组,而不是键,其中元组元素 0 是对象属性的字面值,元组元素 1 是对象本身。有了我的装饰列表后,我可以使用内置的列表排序方法(例如“sorted()”或“.sort()”等)将它们全部排序。

当两个对象具有相同的排序属性值时,我得到一个错误。此示例代码将重现错误:

class Shrubber:
    def __init__(self, age):
        self.name = 'Roger'
        self.age = age

    def getAge(self):
        return self.age

def main():
    rogerAges = [30, 21, 21, 25]
    rogers = []
    for rAge in rogerAges:
        newShr = Shrubber(rAge)
        rogers.append((newShr.getAge(), newShr))
    rogers.sort()
    print(rogers)

main()

我希望程序打印如下内容:

[(21, <__main__.Shrubber object at XxXEX>), (21, <__main__.Shrubber object at YxYEY>), (25, <__main__.Shrubber object at ZxZEZ>), (30, <__main__.Shrubber object at QxQEQ>)]

...但是相反,它给了我一个 TypeError:

TypeError: unorderable types: Shrubber() < Shrubber()

我确定我收到了错误,因为在 Python 遇到两个元组元素 0 的两个相同值后,它会查看元素 1 并找到一个不可排序的数据类型(一个对象)。然而,两个 Shrubbers 的年龄不能相同的限制让我好像错过了什么。

我的问题:我可以按元组元素 0 稳定地对我的元组列表进行排序并忽略我的不可排序的元组元素 1 吗?

【问题讨论】:

  • 而不是?你确定它不是在谈论比较器吗?引入关键功能的原因之一是为了优化您正在谈论的内容。
  • 在 python 2.7 上为我工作
  • 是的,他说不要使用键,而是为每个 2 元素元组创建具有可排序属性的字面值作为元素 0 的元组。他没有在书中介绍比较器,因为它们还没有被介绍过,并且在教科书索引中也没有条目。使用 Python 3.3。
  • 那……没有意义。 key 函数只对每个列表项进行一次评估。就像你使用了 decorate-sort-undecorate 一样,但是更快更干净。我能想到使用装饰列表可能更快的唯一情况是,如果您要反复修改和重新排序列表,但即便如此,如果键函数只是获取一个属性,成本应该微不足道。
  • John Zelle(作者)说:“将函数传递给列表排序方法的一个缺点是它会使排序变慢,因为 Python 需要比较各种项目时重复调用此函数。创建特殊键函数的另一种方法是创建一个“装饰”列表,该列表将使用标准 Python 排序按所需顺序进行排序。” JZ 然后继续描述我提到的元组方案。

标签: python list sorting


【解决方案1】:

我不知道在这些情况下如何让它忽略第二个元素。另一种方法是向对象的类添加一个方法,该方法将返回所需的信息。这样做将避免需要通过sort()a key= 函数。这是我的意思的一个例子:

class Shrubber:
    def __init__(self, age):
        self.name = 'Roger'
        self.age = age

    def getAge(self):
        return self.age

    def __lt__(self, other):  # added comparison method
        return self.age < other.age

def main():
    rogerAges = [30, 21, 21, 25]
    rogers = []
    for rAge in rogerAges:
        newShr = Shrubber(rAge)
        rogers.append((newShr.getAge(), newShr))
    rogers.sort()
    print(rogers)

if __name__ == '__main__':
    main()

输出(为便于阅读而包装):

[(21, <__main__.Shrubber object at 0x00C1D830>),
 (21, <__main__.Shrubber object at 0x00C1D9D0>),
 (25, <__main__.Shrubber object at 0x00C1DA30>),
 (30, <__main__.Shrubber object at 0x00C1D9F0>)]

【讨论】:

  • 谢谢!就我如何编写代码而言,这很清楚,但我必须更多地了解“lt”和类似函数的工作原理。
  • documentationlist.sort() 方法表示它仅使用对应于类的 &lt; 比较对列表进行排序'__lt__ method。我的答案中的代码只是将这样的方法添加到 Shubber 类中,该方法是为了只比较感兴趣的数据属性而编写的。
  • 是的,我现在明白了。在问这个问题之前我从未听说过丰富的比较,但是文档非常简单,一旦我意识到 1) 丰富的比较方法不是显式调用的,而是在比较对象时隐式调用和 2) 丰富的比较仅当对象位于适当运算符的适当一侧时才调用(即,仅当对象位于 &lt; 左侧时才调用__lt__,如您所说)。超级好用。再次感谢您将我指向这些。
【解决方案2】:

如果您确保比较永远不会到达 Shrubber 对象,则不必实现 __lt__ 函数。一种方法是在该元组中插入另一个整数:

def main():
    rogerAges = [30, 21, 21, 25]
    rogers = list()
    for i, rAge in enumerate(rogerAges):
        newShr = Shrubber(rAge)
        rogers.append((newShr.getAge(), i, newShr))
    rogers.sort()
    print(rogers)

但是,从各个角度来看,这都是错误的,您应该使用key= 函数。这更难阅读,根据我的测量,它慢了大约四倍。尽管如此,使用__lt__(或已弃用的cmp=)更糟糕,大约慢了两倍。

最后一点,以这种方式附加到列表不是 Pythonic 方式。你可以这样做:

def main():
    rogerAges = [30, 21, 21, 25]
    rogers = [(rAge, i, Shrubber(rAge)) for (i, rAge) in enumerate(rogerAges)]
    rogers.sort()
    print(rogers)

【讨论】:

  • 谢谢鸸鹋。我会添加代码来计算相似对象的实例是有道理的——我没有想到这一点。
  • 它绕过了书中当时没有涉及的丰富的比较或 lambda 函数。并不是说绕过 lambda 或丰富的比较是正确的或 Pythonic,我只是想知道我该怎么做。我会支持你,但我仅有的 13 个信誉并没有给我这种特权。顺便说一句,SO 声誉系统在我看来很像 Bart Simpson 的游乐场规则,所以这也是我不再为 SO 做出贡献的原因。
  • 是的,规则感觉就像一个奇怪的角色扮演游戏。有时这很有趣,但我很确定我可以没有这些支持点:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多