用 Python 時間也算不短了,但總感覺自己在用寫 C++ 程式碼的思維寫 Python,沒有真正用到其作為指令碼語言的優勢。之前刷 LeetCode 時,自己的 Python 程式碼總是很長,很像披著 Python 外衣的 C++ 程式碼(放在這裡,不斷重構中)。
想來大概是因為覺得python簡單,平時只是零零碎碎的學習,也沒有去讀別人的程式碼,導致掌握的不夠深入。回想起前段時間的面試,面試官看我簡歷寫熟悉Python,就問了兩個Python的問題:
- Python 中常用的優化技巧(能夠提升 Python 執行效率的,除了演算法層面)
- 按照 value 從小到大輸出 dict 中的 key-value值。
我支支吾吾半天,就是沒有答到點上,直接導致被拒(後來整理的內容放在這裡)。所謂知恥而後勇,經過一段時間對 Python 的重新學習,才慢慢發現 Python 的一些強大與美妙之處。
從排序說起!
程式中經常用到排序函式,Python 提供了 sort 和 sorted 函式,一個原地排序,一個返回排序後的新結果,函式原型很簡單:
1 |
sort([cmp[, key[, reverse]]]) |
自己用的最多的類似下面的語句:
1 2 3 4 |
>>> l = [43, 12, 4, 6] >>> l.sort() >>> l [4, 6, 12, 43] |
曾經竊以為這就體現了 Python 的簡單優雅,不像 C++ STL中那樣還需要指定迭代器範圍,然後對 sort 的理解也就止步於此。後來遇到稍微複雜一點的排序場景,自己就 Google-Stackoverflow-Copy,解決了眼前的問題,但是從來沒有去深挖(這也就導致那次面試中中沒有回答出來上面的第二個問題)。
sort 之美
後來去看了下 sort 的函式說明,包括 cmp, key, reverse 引數究竟怎麼去用,又寫了幾個例子,以為這下子對 sort 可謂是理解透徹了。比如要要根據值的大小輸出字典內容,那麼就可以像下面這樣優雅地解決:
1 2 3 |
>>> d = {1: 'z', 2:'y', 3: 'x'} >>> print sorted(d.items(), key=lambda x: x[1]) [(3, 'x'), (2, 'y'), (1, 'z')] |
我甚至可以得到一個根據value排序的字典,只需要用 collections.OrderedDict
即可:
1 2 3 4 |
>>> from collections import OrderedDict >>> sorted_d = OrderedDict(sorted(d.items(), key=lambda x: x[1])) >>> sorted_d OrderedDict([(3, 'x'), (2, 'y'), (1, 'z')]) |
sort 之魅
我以為我對 sort 理解足夠了,直到在 hackerrank 遇到這個題目。
給定一個只包含大小寫字母,數字的字串,對其進行排序,保證:
- 所有的小寫字母在大寫字母前面
- 所有的字母在數字前面
- 所有的奇數在偶數前面
考慮用 sort 函式來完成排序。開始之前,再來看看文件對sort函式中key的說明:
key parameter to specify a function to be called on each list element prior to making comparisons. The value of the key parameter should be a function that takes a single argument and returns a key to use for sorting purposes.
通俗講,key 用來決定在排序演算法中 cmp 比較的內容,key 可以是任何可被比較的內容,比如元組(python 中元組是可被比較的)。所以上面的排序問題可以用下面的程式碼來解決:
1 2 3 |
>>> s = "Sorting1234" >>> "".join(sorted(s, key=lambda x: (x.isdigit(), x.isdigit() and int(x) % 2 == 0, x.isupper(), x.islower(), x))) 'ginortS1324' |
這裡,lambda 函式將輸入的字元轉換為一個元組,然後 sorted 函式將根據元組
(而不是字元)來進行比較,進而判斷每個字元的前後順序。
如果同樣的程式用 C++ 來寫的話,可能需要一個複雜的仿函式,來定義排序的規則,遠沒有 Python 這般簡潔優雅。
再探 Python
Python 是一門簡單方便的語言,相信這是大部分人對 Python 的第一感覺。初學 Python,我們可能痴迷於 Python 的列表解析,list 切片,字典推導,或者是陶醉在各種強大的第三方庫裡,比如網路庫 requests,科學計算庫 numpy,web開發框架 Django 等。
但是實際寫程式中,我們經常會寫出許多繁雜的、醜陋的
Python程式碼。比如要判斷一個數字是否是迴文數字,可能會習慣性地寫出下面這樣的程式碼:
1 2 3 4 5 6 7 8 9 |
def isPalindrome(x): if x < 0: return False reversed_x = 0 original_x = x while x > 0: reversed_x = reversed_x * 10 + x % 10 x /= 10 return reversed_x == original_x |
仔細一看,這簡直就是 C++ 程式碼,完全沒有 Python 的優雅與簡單。那麼,該怎樣寫才能夠顯的 Pythonic 呢?其實,用 Python 的話只要一行就可以啦(這裡不考慮效率,如果考慮效率的話,C++會更加合適,單對這題來說,其實有比上面更高效的方法)!
1 2 |
def isPalindrome(x): return x >= 0 and str(x) == str(x)[::-1] |
那麼如何養成用 Pythonic 的思維解決問題呢?我覺得首先要對 Python 十分熟悉,精通大部分函式以及 Python 的特色:比如裝飾器,迭代器,生成器以等,下面舉幾個簡單的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# 函數語言程式設計 >>> nums = map(int, "123456789" ) >>> nums [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 15 >>> sum(nums) 45 # 生成器 >>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print i ... 0 1 4 >>> for i in mygenerator: ... print i ... # lambda 匿名函式 >>> c = lambda *z: z >>> c( 10, 'test') (10, 'test') # 迭代 >>> l = [i**2 for i in range(9)] >>> l_iter = iter(l) >>> next(l_iter) 0 >>> next(l_iter) 1 >>> next(l_iter) 4 # 資料結構 set >>> set_a = set([i for i in range(1,9,2)]) >>> set_b = set([i for i in range(0,9,2)]) >>> print set_a | set_b set([0, 1, 2, 3, 4, 5, 6, 7, 8]) |
其次,要多讀一些 Pythonic 的程式碼,學習別人如何優雅地使用python。這裡我推薦去看 Leetcode 的 Discuss,裡面有許多驚才豔豔的程式碼。特別推薦 @StefanPochmann,許多程式碼讓我獲益匪淺,比如這裡對 iter() 的使用。
再來看一個問題,按照二進位制位反轉 32 位的一個整形無符號數字。用 Python 可以寫出很簡單直觀的程式碼,如下:
1 2 3 4 |
def reverseBits(n): bit_str = '{0:032b}'.format(n) reverse_str = bit_str[::-1] return int(reverse_str, 2) |
當然,上面不考慮效率,這裡有一個利用分治法思想的高效的方法。
Python 是一門高效、簡單、方便的語言,但這並不意味你不花時間就可以用的很好。
更多閱讀
Sorting Mini-HOW TO
sort()中cmp引數的用法
hackerrank: ginortS
Sort a Python dictionary by value
Python高階程式設計技巧
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式