首先,寫這篇文章的起因,是這兩天玩了一個網站 http://www.checkio.org 用Python做題、出題,交流,很有意思也很有挑戰。
————————————進入正題——————————————————————————
其中一道字串處理的題目如下:http://www.checkio.org/mission/implementation/1088/python-27/ 簡單來說,輸入是一個字串,輸出也是一個字串。我們要做的,是讓輸入中的整數每隔三位元加一個小數點。
測試案例: assert checkio('123456') == '123.456' assert checkio('333') == '333' assert checkio('9999999') == '9.999.999' assert checkio('123456 567890') == '123.456 567.890' assert checkio('price is 5799') == 'price is 5.799' assert checkio('he was born in 1966th') == 'he was born in 1966th'
題意應該是表達清楚了。我自己寫了一段代碼,大概有20~30行的樣子,用Regex把數字提取出來,然後改掉,然後一段一段填回去。比較麻煩的地方,是修改字串之後,長度增加了,所以之前在提取字串的時候要自己處理當前插入位置之類的。
Pass這道題之後,看了下Best solution: def checkio(txt): ''' string with comma separated numbers, which inserted after every third digit from right to left ''' return re.sub(r'(?<=\d)(?=(\d\d\d)+\b)', '.', str(txt))
看過之後發現自己對python的加強版Regex不夠熟悉,看了看這篇文章,補了補功課: PythonRegex介紹 http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html 上面的解法非常巧妙,注意一點:(?=...)這種匹配模式是不消耗字元的,所以會形成迴圈匹配,正好達到了目的。我的意思是:如果不是迴圈匹配的話,加一個小數點之後可能就繼續下一個詞了。
經過實驗,(?<=...)這種特殊構造有一個限制條件——必須是固定長度的,如果你寫(?<=.*?)這種條件,Python會報相應的異常。
————————————re.sub函數———————————————————— 這個題的解法很巧妙,當然巧妙也意味著比較特別,通用性不強。 回到之前我的那個破代碼,貼出來瞅瞅:def checkio(txt): ''' string with dot separated numbers, which inserted after every third digit from right to left ''' l = [] beg = 0 for e in re.finditer(r'\b\d+\b', txt): p1, p2 = e.span() l.append( txt[beg:p1] ) s = txt[p1:p2] s2 = [] for i in range(len(s)-1, -1, -1): s2.append(s[i]) if (len(s)-i) % 3 == 0 and i != 0:
s2.append('.') s2.reverse() s = ''.join(s2) l.append( s ) beg = p2 l.append( txt[beg:] ) print ''.join(l) return ''.join(l) 中間比較長的一段是給字串加小數點,假如抽出來不看的話,那麼多餘的地方就是 l、beg、p1、p2等幾個變數以及它們的計算了。 仔細玩了下re.findall和re.sub,發現它們尋找的方式不大一樣,主要是對括弧括起來的group處理不太一樣。這個問題以前也納悶過。今天恍然大悟:
re.sub的第二個參數repl,可以是函數!
如果自訂一個函數change(match),在使用re.sub時底層就會在尋找到合適的pattern時調用這個change函數。參數是一個match對象,記錄了group、span之類的東東。處理這個match對象返回一個字串,底層就會幫你把這個字串替換回去。 不容易說明白,試試就明白了。
我用這個辦法修改了之前MB檔案處理的函數,簡潔了很多。re.sub函數的介面設計很自然,值得學習。以前竟然沒想到能這麼用,小自責一下。