Python自定义比较器如何工作?

时间:2015-04-19 07:14:08

标签: python python-2.x

我有以下Python词典:

[(2, [3, 4, 5]), (3, [1, 0, 0, 0, 1]), (4, [-1]), (10, [1, 2, 3])]

现在我想根据字典值的总和对它们进行排序,因此对于第一个键,值的总和是3 + 4 + 5 = 12.

我编写了以下代码来完成这项工作:

def myComparator(a,b):
    print "Values(a,b): ",(a,b)
    sum_a=sum(a[1])
    sum_b=sum(b[1])
    print sum_a,sum_b
    print "Comparision Returns:",cmp(sum_a,sum_b)
    return cmp(sum_a,sum_b)

items.sort(myComparator)
print items

这是我在上面运行后获得的输出:

Values(a,b):  ((3, [1, 0, 0, 0, 1]), (2, [3, 4, 5]))
2 12
Comparision Returns: -1
Values(a,b):  ((4, [-1]), (3, [1, 0, 0, 0, 1]))
-1 2
Comparision Returns: -1
Values(a,b):  ((10, [1, 2, 3]), (4, [-1]))
6 -1
Comparision Returns: 1
Values(a,b):  ((10, [1, 2, 3]), (3, [1, 0, 0, 0, 1]))
6 2
Comparision Returns: 1
Values(a,b):  ((10, [1, 2, 3]), (2, [3, 4, 5]))
6 12
Comparision Returns: -1
[(4, [-1]), (3, [1, 0, 0, 0, 1]), (10, [1, 2, 3]), (2, [3, 4, 5])]

现在我无法理解比较器是如何工作的,传递了哪两个值以及会发生多少次这样的比较?它是否在内部创建了一个排序的键列表,它跟踪每个比较?这种行为似乎也很随机。我很困惑,任何帮助都会受到赞赏。

4 个答案:

答案 0 :(得分:2)

数字和进行的比较没有记录,事实上,它可以从不同的实现中自由改变。唯一的保证是,如果比较函数有意义,该方法将对列表进行排序。

CPython使用Timsort algorithm对列表进行排序,所以你看到的是该算法执行比较的顺序(如果我没有误认为很短的列表,Timsort只使用插入排序)< / p>

Python 跟踪&#34;键&#34;。它只是在每次进行比较时调用您的比较函数。因此,您的函数可以调用超过len(items)次。

如果要使用密钥,则应使用key参数。事实上你可以这样做:

items.sort(key=lambda x: sum(x[1]))

这将创建键,然后使用键上的常用比较运算符进行排序。这可以保证keylen(items)次传递函数。


鉴于您的清单是:

[a,b,c,d]

您看到的比较顺序是:

b < a   # -1  true   --> [b, a, c, d]
c < b   # -1  true   --> [c, b, a, d]
d < c   # 1   false
d < b   # 1   false
d < a   # -1  true   --> [c, b, d, a]

答案 1 :(得分:1)

  

比较器如何工作

这很好documented

  

比较两个对象x和y并根据结果返回一个整数。如果x <1,则返回值为负。 y,如果x == y则为零,如果x> 0则严格为正收率

而不是调用cmp函数,你可以写:

sum_a=sum(a[1])
sum_b=sum(b[1])
if sum_a < sum_b: 
   return -1
elif sum_a == sum_b:
   return 0
else:
   return 1
  

传递了两个值

从您的打印语句中,您可以看到传递的两个值。让我们看看第一次迭代:

((3, [1, 0, 0, 0, 1]), (2, [3, 4, 5]))

这里打印的是一个元组(a,b),因此传递给比较函数的实际值是

a = (3, [1, 0, 0, 0, 1])
b = (2, [3, 4, 5]))

通过您的函数,您可以比较每个元组中两个列表的总和,您可以在代码中表示sum_a和sum_b。

  

会发生多少次这样的比较?

我猜你真正问的是:通过调用一个函数,排序是如何工作的?

简短的回答是:它使用Timsort算法,并且它调用比较函数O(n * log n)次(注意实际的调用次数是c * n * log n,其中c &gt; 0)。

要了解正在发生的事情,请想象自己对值列表进行排序,比如v = [4,2,6,3]。如果你系统地讨论这个问题,你可能会这样做:

  1. 从第一个值开始,索引为i = 0
  2. 将v [i]与v [i + 1]
  3. 进行比较
  4. 如果v [i + 1]&lt; v [i],交换他们
  5. 增加i,从2开始重复,直到i == len(v) - 2
  6. 从1开始直到没有进一步的掉期
  7. 所以你得到了,i =

    0: 2 < 4 => [2, 4, 6, 3] (swap)
    1: 6 < 4 => [2, 4, 6, 3] (no swap)
    2: 3 < 6 => [2, 4, 3, 6] (swap)
    

    重新开始:

    0: 4 < 2 => [2, 4, 3, 6] (no swap)
    1: 3 < 4 => [2, 3, 4, 6] (swap)
    2: 6 < 4 => [2, 3, 4, 6] (no swap)
    

    再次开始 - 没有进一步的互换,所以停止。您的列表已排序。在这个例子中,我们已经完成了3次列表,并且有3 * 3 = 9次比较。

    显然这不是很有效 - sort()方法只调用你的比较器函数5次。原因是它采用了比上述简单算法更有效的排序算法。

      

    此外,这种行为似乎非常随机。

    请注意,通常不会定义传递给比较器函数的值序列。但是,sort函数会对它接收的iterable的任意两个值进行所有必要的比较。

      

    是否在内部创建了一个按键的排序列表,以便跟踪每个比较结果?

    不,它没有在内部保留密钥列表。相反,排序算法基本上遍历您给出的列表。事实上,它构建了列表的子集,以避免进行太多的比较 - 通过Visualising Sorting Algorithms: Python's timsort

    可以很好地显示排序算法在Aldo Cortesi的工作方式。

答案 2 :(得分:0)

基本上,对于简单的列表,例如[2,4,6,3,1]和您提供的复杂列表,排序算法是相同的。

唯一的区别是列表中元素的复杂性以及如何比较任何两个元素(例如您提供的myComparator)的比较方案。

Python排序有一个很好的描述:https://wiki.python.org/moin/HowTo/Sorting

答案 3 :(得分:0)

首先,cmp()函数:

cmp(...)
    cmp(x, y) -> integer
    Return negative if x<y, zero if x==y, positive if x>y.

您使用的是以下行:items.sort(myComparator),相当于说:items.sort(-1)items.sort(0)items.sort(1)

由于您希望根据每个元组列表的总和进行排序,您可以这样做:

mylist = [(2, [3, 4, 5]), (3, [1, 0, 0, 0, 1]), (4, [-1]), (10, [1, 2, 3])]
sorted(mylist, key=lambda pair: sum(pair[1]))

我认为,这正是你想要的。根据每个元组列表的mylistsum()进行排序