如何让这个天真的python实现Quicksort更加pythonic?

时间:2010-12-01 09:22:17

标签: python quicksort

我在阅读 diveintopython 的两个小时后,我实现了一个简单版本的快速排序。

import operator

def even(num):
    return operator.mod(num,2) == 0

def last(list):
    return len(list)-1

def median(list):
    if even(len(list)):
        return len(list)/2 - 1
    else:
        return len(list)/2

def sort(list, pivot_selector):
    if len(list) <= 1:
        return list
    else:
        i = pivot_selector(list)
        pivot = list[i]
        less, greater, equal = [], [], []
        for x in list:
            if x < pivot:
                less.append( x )
            elif x == pivot:
                equal.append( x )
            else:
                greater.append( x )

    return sort(less, pivot_selector) + equal + sort(greater, pivot_selector)

if __name__ == "__main__":
    print sort([5,4,3,2],median)
    print sort([],median)
    print sort([2],median)
    print sort([3,2],median)
    print sort([3,2,3],median)
    print sort([1,3,2],median)
    print sort([1,'a',0],median)
    print sort([None,1,0],median)

5个问题:

  1. 此代码驻留在名为quicksort.py的文件中。如何隐藏甚至方法导出到公众。
  2. 传递* pivot_selector *作为参数是pythonic吗?
  3. 我的快速配置实施有什么不对或不理想吗?
  4. 您如何允许用户指定一个比较器,该比较器将对列表元素强制执行自定义排序?
  5. 是否有一种pythonic方法来强制执行约束,list参数必须包含同类型?

7 个答案:

答案 0 :(得分:5)

  

1 - 此代码驻留在名为quicksort.py的文件中。如何将该方法隐藏起来,甚至将其导出为公众。

惯例是将函数命名为模块with an underscore at the beginning的私有函数。

  

2 - 传递* pivot_selector *作为参数是pythonic吗?

由于Python具有第一类函数,因此可以将函数作为参数传递。

  

3 - 我的快速配置实施有什么不对或不理想吗?

使用equal列表对我来说似乎不是传统的。 Usually items equal to the pivot end up in the greater list.

  

4 - 您如何允许用户指定一个比较器来对列表元素强制执行自定义排序?

标准Python sort()sorted()函数具有比较函数的可选参数。这似乎是最好的方法。

  

5 - 是否有一种pythonic方法来强制执行list参数必须包含同类型的约束?

在Python中,您通常不担心这一点。 Python有concept of duck-typing,所以如果一个对象做了它应该做的事情,我们不担心事先检查它的类型。这通常表达为“请求宽恕比获得许可更容易。”

因此,让模块的用户担心如果他们传入一个无法相互比较的对象列表,则会抛出异常。

答案 1 :(得分:4)

关于代码的一些半随机说明:

  • 为什么在operator.mod中明确even而不只是%
  • len(list)/2 - 1median真的很重要吗?如果你有一个长度为4的列表,为什么索引2的中位数小于索引1?此外,middle将是一个更合适的名称,因为该函数并不真正计算中位数。在快速排序列表中查找实际中位数是一个非常复杂的问题,通常是近似的。
  • 将选择器作为函数传递是非常Pythonic。您可以使用相同的方法传递比较函数,并在sort
  • 中使用它
  • 你的问题(5)闻起来非Pythonic - 不要这样做。 Python就是鸭子打字 - 如果你的用户认为他想要将整数与class UserID进行比较并提供适当的方法/操作符,那就让他吧。

答案 2 :(得分:1)

我觉得这很好。我喜欢枢轴选择器的函数参数。

一些意见:

  • 不要影响像listsort
  • 这样的内置组件
  • 使用li [-1]从列表中获取最后一个元素
  • even函数看起来有点多余。

答案 3 :(得分:0)

  1. 你不用Python隐藏东西,我们都同意这里的成年人。如果你真的不想导出它,你可以使它成为一个内部函数(在你正在使用它的函数内声明它)。
  2. 我不明白为什么它不会是pythonic。传递函数参数通常用于所有Python API(例如sort的{​​{1}}参数)
  3. 是的,您的中位数应称为“中间”。当然,正如您可能已经知道的那样,您可以对其进行排序并使用基于堆栈的迭代替换递归,这将更快一些(因为调用堆栈帧比推送和弹出更重要而不仅仅是参数而不是更改)。
  4. 与您的pivot选择器参数相同,传递一个带有两个参数的函数,如果参数是有序的,则返回true。
  5. 不,但如果您愿意,可以提出异常。如果你真的,真的想,你可以使用 numpy 数组,确保它们是同质的。

答案 4 :(得分:0)

我唯一想补充的是,创建三个列表并一次增加一个元素看起来相当不理想。典型的快速排序实现(在Python中或不在Python中)会在现有列表中移动元素。

答案 5 :(得分:0)

  

2:是的。但使用默认值可能会更好。

     

5:isinstance。但不是一个好的选择。确保他们   都有必要的界面就好了,你没有   必须监控他们的类型。

答案 6 :(得分:0)

实现排序算法的Pythonic方法不是。 ;)内置的sort有一个原因,那就是只有一种方法可以做到。

那说:

  • 对于简单模数运算,无需import operator。 Pythonistas理解%符号。

  • 但我们甚至不需要单独检查列表长度的奇偶校验,因为Python会进行整数除法。

  • 在这一点上,将median真正用于函数是没有意义的,因为它所做的工作非常简单。我们也不需要last,因为Python允许您使用负数索引列表并从最后开始计算。 mylist[-1]是获取最后一个元素的惯用的Pythonic方法。

  • 一般编程实践:不要做任何事情 - 即在每次计算中为每个中间结果设置一个单独的变量。变量适用于具有足够重要名称的事物。如果你不能想到一个比“我”更具描述性的名字,那么这可能表明你并不需要打破它。

  • 正如其他人所提到的,不要影响内置。应该列出的变量的一个常见约定是将它们称为a_list。 (这来自Smalltalk世界,IIRC。)

  • 使用列表推导(和/或内置函数mapfilter来尝试)尽可能将列表处理到其他列表中。

  • 我会说'pivot_selector'应该实际计算阈值,而不是位置。毕竟,没有理由认为算法正确性要求'pivot'实际上在数组中:)

    def middle(a_list): return a_list[(len(a_list) - 1) / 2]
    
    
    def last(a_list): return a_list[-1]
    
    
    def quick_sort(a_list, pivot_selector):
      if len(a_list) <= 1: return a_list
      pivot = pivot_selector(a_list)
      return (
        quick_sort([x for x in list if x < pivot], pivot_selector) +
        [x for x in list if x == pivot] +
        quick_sort([x for x in list if x > pivot], pivot_selector)
      )