Python 有一個 cmp() 函數,主要目前是用來比對兩元素,並回傳其結果。(P.S. 這個函數在 Python v3 是沒有的)
這個函數也可以用來比較兩個清單,例如:cmp(["John", "Smith"], ["John", "Good", "Boy"]),最後,再根據比較結果,得出 1, 0 以及 -1 三個值。
前些日子,在網路上看到一段範例如下
#!/usr/bin/python list1, list2 = [123, 'xyz'], [456, 'abc'] print cmp(list1, list2); print cmp(list2, list1); list3 = list2 + [786]; print cmp(list2, list3)
它們最後回傳的結果,分別為 -1, 1, -1,這就讓我有點丈二金剛摸不著頭緒了!腦袋瓜兒瞬間打結,有如九彎十八拐一樣!找了下 Python 官方說明,非常簡單的一小段話
Compare the two objects x and y and return an integer according to the outcome. The return value is negative if x < y, zero if x == y and strictly positive if x > y.
OK,這樣子就明白一點點了。有時我習慣講一個函數時,會習慣用最簡潔的方式進行描述。
以 cmp() 為例,真正模樣是 cmp(x,y),藉由原型以及官方說明,我可以暫時得到這樣的結論
- x > y,得到 1 的結果
- x = y,得到 0 的結果
- x < y,得到 -1 的結果
到目前為止,這指的是兩個值,也就是 X 和 Y 的比較。只是單純的比大小,大概我家的小朋友都比我厲害多了。
不過,範例中,是比較兩個清單。就我的觀念及想法,清單的比較,應該是
- 哪個清單的項目多,誰就是大的
- 兩清單項目數相同,何清單的最大值,是最大的,則它就是大的
根據範例的說明,是這樣提到的
If elements are of the same type, perform the compare and return the result. If elements are different types, check to see if they are numbers.
- If numbers, perform numeric coercion if necessary and compare.
- If either element is a number, then the other element is "larger" (numbers are "smallest").
- Otherwise, types are sorted alphabetically by name.
If we reached the end of one of the lists, the longer list is "larger." If we exhaust both lists and share the same data, the result is a tie, meaning that 0 is returned.
根據範例說明,大概又可以得到幾點比較的條件
- 如果同屬數字,那就是直接比大小
- 但如果,有一邊不是數字,那麼非數字是大的,也就是數字是小的
- 或者根據類型的英文名稱進行比較
- 再或者根據清單的長度,進行比較。或說項目個數較多者,為大
- 直到最後,已經無法比較了,則視為平局,也就是會得到 0 的結果
看起來沒有什麼問題。只是不解的是,為什麼 list1 和 list2 一對調,其結果就會完全不一樣。在我理解上,不論有沒有對調,結果應該會是一樣的。
沒搞懂問題和原因,真的會讓我很毛躁,也很不開心。索性開始進行一些模擬測試,再試著去找出原因。
總算推論出一個可以說服我自己的情況了!範例中的兩清單如下
- list1 = [123, "xyz"]
- list2 = [456, "abc"]
如果,在清單的比較,那麼它們在實際比較上,可能會比較像是以這順序進行比較
- cmp(123, 456)
- cmp("xyz", "abc")
有趣的是,它只會回傳第一個的比較值,這部份我認為概念上有點像跆拳道的驟死賽了,誰先得分,誰就獲勝了。
所以,若是 cmp(list1, list2) 的情況下,因為 123 和 456 都是清單中的第一個項目,所以它們倆先出來 PK,就如同我提的,很像進入了驟死賽,123 和 456 先出陣對踢,結果 123 輸了 456,也因此會回傳了 -1 的結果,後面的 xyz 和 abc 也不用再出來比了。
反之,cmp(list2, list1) 的情況下,狀況和 cmp(list1, list2) 可以說是一樣或極為相似的,但是為什麼反而 cmp(list2, list1) 會得到 1 的結果?
如果還記得 cmp(x, y) 的比較,那麼 cmp(list2, list1) 就會變成像這樣的結果
- cmp(456, 123)
- cmp("abc", "xyz")
發現了嗎?456 之如 x,123 之如 y,而 x > y,會得到一個 1 的結果,又像在踢驟死賽,所以 456 > 123,立馬得 1 分,又因為 cmp 只回傳第一次的比較值,所以 cmp(list2, list1) 就會得到 1 的結果。
但如果兩個清單的項目,都是文字呢?像這樣
- list1 = ["John", "Smith"]
- list2 = ["John", "Simon"]
cmp(list1, list2),其結果會得到 1,cmp(list2, list1) 則會得到 -1
如果我們把它們拆開,會像這樣
cmp(list1, list2)
- cmp("John", "John")
- cmp("Smith", "Simon")
cmp(list2, list1)
- cmp("John", "John")
- cmp("Simon", "Smith")
等等,我稍早明明說「只會回傳第一個比較」的結果,這麼又變卦了!是有沒有這麼「女人心海底針」呀!
明明 John = John,那應該要得到 0 呀,為什麼會是 1 或 -1!
其實,Python 的 cmp 在比對上會「盡量公平」,有點像跆拳道的驟死賽和足球 PK 賽兩者的混合體。如果上陣的運動員,最後還是無法分出勝負,才會以和局收。
在這裡,John V.S. John,以和局收。但兩方還有一個選手呢!所以,Smith V.S. Simon 或 Simon V.S Smith,文字的比較,又不像跆拳或足球,反而像是在翻字典,記得四個字:撐久必勝。
以翻字典來講,Simon 會比 Smith 更早在字典裡出現,還記得撐久必勝吧,也就是 Smith 是比 Simon 大的。這也是為什麼,前面兩個比較,會分別得到 1 和 -1 的結果了。
猜猜看,它們分別得到什麼答案
- cmp([2], [1, 99, 10000])
- cmp(["0"], [100, 1, 0])
- cmp([1, "1", 0], [1, 99, 10000])