thinker 寫了一篇很有趣的文章 心靈與程式碼的協奏曲. 也引發了我的一些想法. 不過我 coding 功力完全比不上 thinker. 以下只是我的一些個人感想而已.
假設問題是, function 接受一字串,裡面的每一行有兩個欄位,皆是整數。欄位以一個空白相隔。 function 必把每一行的兩個欄位的數字相乘,然後再將每一行的結果加總。
雖然 thinker 強調這種回推法或者Top-Down的設計方式的不同, 但是我倒覺得這並不是設計方法的不同而是工具的不同, 而且即使採用回推法, 如果之前的設計不好, 最後還是要回去修改原來的程式, 這跟傳統的做法沒有太大的差別. 以這個例子來說, thinker 一開始想到的是,
result = sum(value_of_lines)但是這裡其實就是一個有趣的地方, 為什麼 values 會被存放在 list 裡面? 原因不就是因為 Python 大量的建議使用 list 作為基本的資料結構? 我基本上還是比較習慣一行一行往下寫的做法, 但是因為工具 (list comprehension) 的關係, 我想到的程式是
001 def mul_n_sum1(data): 002 bag=[ line.split() for line in data.strip().split('\n')] 003 result=0 004 for i in [ int(i[0])*int(i[1]) for i in bag]: 005 result+=i 006 return result和 thinker 的程式相去無幾了. 更精簡一點, 我可能寫成這樣.
001 def mul_n_sum2(data): 002 bag=[ line.split() for line in data.strip().split('\n')] 003 return sum([ int(i[0])*int(i[1]) for i in bag])以我寫的程式來說, 兩種風格都有, 決定會用 for-loop 或者 list comprehension 基本上是依據
- 有沒有我已知的方便的函數
- Data 的大小
- for-loop 裡內容的複雜度
- 有沒有想把程式寫短一點的慾望
001 def mul_n_sum3(data): 002 bag=[ line.split() for line in data.strip().split('\n')] 003 return reduce(lambda x,i: x+int(i[0])*int(i[1]), bag,0)第二和第三點當然也非常重要, 我處理的資料通常是幾GB的大小, 我不太會在這種情況下採用 list comprehension.
對我來說, 一般而言, 寫程式比較接近填空, 先搞清楚自己要做什麼事, 然後大概幻想一下流程 (如果之前寫過類似的程式,流程圖會很快出現), 然後把流程圖裡的方塊一個一個填進去並實作出來. 由於 Python 本來就想要加入 functional programming 的特色, 而且 list 又是對 Python 來說最自然的資料結構, 用 list 串起每個方塊是很自然的想法, 因此寫出類似的東西, 或者說有點 FP 風格的 code 並不奇怪.
當 thinker 一開始決定用
result = sum(value_of_lines)來實作時, 其實已經限制了接下來的回推要怎麼做了. 如果今天是用C語言的話, 往上回推的寫法大概還是出現 function, recursive function, for-loop 幾種寫法吧. 因為自己實作 dynamic list/array 可能還比較麻煩XD 如果是要實作迴圈來加總, 其實又會碰到一樣的問題了.
我假設自己在C裡面用倒推法寫程式, 第一步是
result = sum(value_of_lines)然後我們就要設計 value_of_lines 是怎麼來的, 如果是在 C 語言裡, 我直覺有兩種做法
value_of_lines=multiple_pairs(two_D_array)或者
value_of_lines=multiple_two_vector(column_1,column_2)可以想像, 這兩種函式不論怎麼寫, 大概都要用到 for-loop/recursive. 所以下一步,我們應該思考什麼? 如果不考慮細節, 這個時候我們要開始設計資料如何存入對應的資料結構. 如果是方法一, 大概就是如下
two_D_array=parse_data_line_by_line(data)如果是方法二, 就比較麻煩了, 我們必須一次寫兩行,
column_1=parse_data_line_by_line_and_fetch_column(data,1) column_2=parse_data_line_by_line_and_fetch_column(data,2)或者多幾個步驟
struct_of_two_vector=parse_data_line_by_line(data) column_1=struct_of_two_vector->column_1 column_2=struct_of_two_vector->column_2顯然麻煩多了. 而且繼續寫之後會發現, 我們需要自定一些資料結構, 才能夠保存 array 的長度的資訊. 否則就得 "回頭" 去改函式的介面.
但通常我們不會選擇這條路徑. 因為在寫當下的程式之前, 我們已經稍微往前設想到資料的特性 --- data 被 parse 出來大概長怎樣.
如果用 Python 做這樣的動作, 產生的程式碼大概如下
001 def mul_n_sum(data): 002 txt_field_lines=[ line for line in data] 003 txt_field1 = [ int(line.strip('\n').split()[0]) for line in txt_field_lines] 004 txt_field2 = [ int(line.strip('\n').split()[1]) for line in txt_field_lines] 005 value_of_lines = [ txt_field1[i]*txt_field2[i] for i in xrange(len(txt_field1))] 006 result = sum(value_of_lines) 007 return result雖然比之前的程式沒有複雜太多, 但是我寫起來可一點都不覺得順暢XD 而差異僅僅只是源自 "資料來自於兩個 lists 或者 一個 list"
當我們用 for-loop 寫程式時, 基本上我們要考慮到 pre-condition, loop-content, post-condition 的正確性. 因為 list 的強大, 所以在設計時可以很容易的 "遷就" 上一個流程方塊裡的內容, 所以很適合拿來做方塊之間傳遞資料的容器. 我是覺得, 這才是用"回推法"寫程式的有效益的主因.
附帶一提,原始命題也暗示了處理的方式,如果說原來的題目描述如下,或許讓人想要用兩個向量相乘的作法。
假設問題是, function 接受一文字檔,裡面有兩直行,每一直行代表一整數向量,欄位以一個空白相隔。 function 求出兩向量之內積。
Ref.: fcamel 技術隨手記: 用 Ruby 寫碼為例, 說明思考和寫程式同步的樂趣
沒有留言:
張貼留言