兩年前舟茶,我在高中母校舉辦的夏令營上做了關于信息學競賽的報告。兩年前堵第,我告訴同學們吧凉,編程是一種技能,每個人都可以且應該掌握踏志,就如同駕駛一樣阀捅。兩年前,我瀏覽同學們的反饋针余,有一個女生寫道饲鄙,駕駛不僅僅是一種技能,人們還能從中獲得享受圆雁,比如駕駛一輛好車忍级。我陷入沉思,編程在作為一種技能的同時伪朽,還有沒有其他屬性颤练,難道也如駕駛一般,用一臺更好的電腦編程更能獲得享受驱负?兩年后嗦玖,回過頭來發(fā)現(xiàn)自己的思考實在荒謬:人們從編程中當然能獲得享受,但這種享受與硬件條件關系甚少跃脊,而更多源自編程本身宇挫。駕駛可以說是操控載具的藝術,而編程酪术,則是操控數(shù)據(jù)的藝術器瘪。
數(shù)據(jù)
當我談編程時,我想說的是數(shù)據(jù)绘雁。這是一個濫用“大數(shù)據(jù)”的時代橡疼,那么“數(shù)據(jù)”究竟是什么呢?整數(shù)庐舟?實數(shù)欣除?還是字符串?不對挪略,這些都只是數(shù)值历帚,數(shù)據(jù)本身應該是一種更抽象的東西滔岳。可以拿表格來舉例子挽牢,一張表格就可以認為是數(shù)據(jù)谱煤。能不能把表格視作與整數(shù)、實數(shù)禽拔、字符串一樣刘离,只不過是另一種數(shù)值呢?恐怕不太適合睹栖,表格有一個很明顯的差異硫惕,就是它是有結構的,即表格有一些內(nèi)在的結構(如行列)磨淌,而數(shù)值填充在這結構中疲憋。所以,數(shù)據(jù)是有結構的數(shù)值集合梁只,而數(shù)值缚柳,則是相對無結構的信息實體。
人們從實踐中獲得各種數(shù)值搪锣,為了分析其意義秋忙,往往按照一定的方式對數(shù)值進行結構化,得到適于分析的數(shù)據(jù)构舟。初中的時候每逢大型考試灰追,老師們在批改結束后會將每個學生每個科目的得分一一輸入電腦,對每個學生生成一個元組狗超,包括了學號弹澎、姓名、班級努咐、分數(shù)等屬性的數(shù)值苦蒿,最后把所有的元組聚集在一起構成數(shù)據(jù)庫,此時就可以做各種分析了(如平均分)渗稍。那么佩迟,人們?yōu)槭裁匆@么做呢?所有人的試卷堆在一起竿屹,想算平均分一張一張加就好了报强,為什么要花時間整理成結構化數(shù)據(jù)呢?原來拱燃,人們不僅僅想算平均分秉溉,可能還要算最高分、及格率什么的,這時直接操作原始數(shù)值的堆砌就顯得很笨拙了坚嗜。整理得到結構化數(shù)據(jù)夯膀,能夠幫助更方便地重用數(shù)據(jù)诗充。
還是考試的問題苍蔬,現(xiàn)在我們得到數(shù)據(jù)了,可以得心應手地做各種分析了蝴蜓,但很快我們又發(fā)現(xiàn)了新問題:最高分碟绑、平均分等任務操控數(shù)據(jù)的方式其實是非常雷同的,但我們卻重復了一遍又一遍茎匠。人力成本是很高的格仲,如果有一個機器能幫助我們完成各種相似的分析過程,該多好诵冒!但問題在于凯肋,我們顯然不可能為每種數(shù)據(jù)操控任務去造一臺機器。這個問題要怎么解決呢汽馋?
前輩們已經(jīng)給出了答案侮东,就是通用數(shù)據(jù)操控機器。什么叫“通用”呢豹芯?前輩們觀察到悄雅,很多任務都可以分解為很多小的動作,比如算平均分铁蹈,就可以分解為把兩個數(shù)加起來放到一個地方宽闲,以及把兩個數(shù)作除法放到一個地方,這兩個小的動作握牧。如果我們能找到一個“小動作”集合容诬,由這些“小動作”的各種組合可以進行多種多樣的數(shù)據(jù)操控,然后我們再為每個小動作找到一種高效的機械實現(xiàn)沿腰,那么下一次我們要處理數(shù)據(jù)時览徒,就可以把其“小動作”序列輸入給這個機器,機器不就可以自動地去處理數(shù)據(jù)了嗎矫俺!
如果你認可上面的解答吱殉,那你已經(jīng)觸及到編程的核心了。所謂編程呢厘托,就是在給定“小動作”集合的前提下友雳,對于一個數(shù)據(jù)處理任務,合理地組織“小動作”構成操作序列铅匹,使得其正確而高效地完成任務押赊。
算法
當我談編程時,我想說的是算法。上面提到流礁,編程的核心就是對數(shù)據(jù)處理任務寫出正確而高效的操作序列涕俗。這乍一看是個很容易的問題——不就是用機器模擬人的動作嘛!但別忘了神帅,這個通用機器相比起人再姑,其能力是很受限的,優(yōu)勢也許僅僅在于完成“小動作”時能比人更快找御。舉個例子元镀,假設我們要把所有學生的數(shù)據(jù)按學號排序,而我們能使用的“小動作”又只有簡單的數(shù)據(jù)比較和數(shù)據(jù)移動呢霎桅?一個簡單的想法是栖疑,就像打撲克時大家摸牌的方法一樣,每次取出一個元組滔驶,將它與前面排好序的元組一一比較遇革,并插入到合適的位置。這個方法固然正確揭糕,但它效率如何呢萝快?真的高效嗎?
還有一個或許不太容易理解的方法插佛,就是我們先把所有學生元組均分為兩組杠巡,然后兩組分別排序,然后將兩組排序的結果合併在一起雇寇。注意在描述中出現(xiàn)了“兩組分別排序”氢拥,這其實是一個自引用,即分別排序時使用的方法也是:將元組分成兩組锨侯,然后再分別排序嫩海。舉個例子,對下面8個數(shù)進行排序:
[8,7,2,1,4,5,6,3]
先分為兩組:
[8,7,2,1] [4,5,6,3]
為了省篇幅就拿第一組繼續(xù)演示:
[8,7] [2,1]
[8] [7] [2] [1]
當分到每組只有一個時囚痴,自然可以認為這組已經(jīng)排好序了叁怪,所以我們可以合并結果:
[7,8] [1,2]
再合并:
[1,2,7,8]
假設第一次分的時候的另外一組也這樣排好了,得到:
[1,2,7,8] [3,4,5,6]
最后合并:
[1,2,3,4,5,6,7,8]
例子很簡單深滚。那么問題來了奕谭,這個方法對任意的序列都正確嗎?高效嗎痴荐?與上一段的逐一插入的方法孰優(yōu)孰劣血柳?
這并不是一個那么容易回答的問題,而這還僅僅只是一個簡單的數(shù)據(jù)處理任務生兆。人們?yōu)榱搜芯窟@個問題难捌,提出了算法,即對“小動作”操作序列的正式稱謂,并發(fā)展出各種各樣的研究:從分析算法的正確性根吁、效率员淫,到各種算法設計的方法。實際上击敌,算法設計與分析是一個很大的領域介返,人們針對各種各樣的問題設計出了很多優(yōu)美的算法,難怪大家會感嘆算法設計的藝術愚争。有前輩說過映皆,“程序=數(shù)據(jù)結構+算法”挤聘,由上面的討論轰枝,這個斷言便很好理解了吧。
抽象
當我談編程時组去,我想說的是抽象鞍陨。這也是我認為編程是一種藝術的原因。這里的抽象从隆,與印象派那種抽象是不一樣的诚撵。編程里的抽象,與所謂的建模有點類似键闺。人們在解決不同的問題時寿烟,往往會建立不同的模型。如果我們把一些建模的過程給串起來辛燥,即在一個模型之上再構建一個模型(后者某種程度上依賴前者)筛武,那么不同的模型相當于可以解決不同層次的問題,這就叫做抽象挎塌。
舉個例子徘六,還是剛才的考試數(shù)據(jù)處理,從原始數(shù)值到有結構的學生信息元組數(shù)據(jù)榴都,可以認為后者是對前者的抽象——我們建立了學生信息的模型待锈;然后,我們?yōu)榱私⑼ㄓ脭?shù)據(jù)操控機器嘴高,設計了一套操控數(shù)據(jù)的“小動作”竿音,這套“小動作”就是在學生信息模型上建立起新的抽象,我們可以通過這個層級上的“小動作”去操控相對底層的數(shù)據(jù)拴驮。還能進一步抽象嗎春瞬?比如我們的任務常常需要對一列數(shù)求和,那么我們完全可以在“小動作”層上再做一層抽象莹汤,提供數(shù)列的求和接口快鱼,那么我們在編程時可以直接用這個接口對一列數(shù)求和,而不用觸碰相對底層的“小動作”。
從這個例子中可以看出抹竹,抽象是編程解決問題中至關重要的方法线罕,因為我們使用的是通用數(shù)據(jù)操控機器,而我們解決的問題卻是領域特定的窃判,這個領域可能有它自己的描述系統(tǒng)钞楼。那么,我們可以通過抽象袄琳,在通用數(shù)據(jù)操控層上設計領域特定模型询件,為解決領域特定問題提供接口。其實唆樊,抽象正是一種設計宛琅,從而它使得編程成為了一種藝術。
電腦上安裝的操作系統(tǒng)逗旁,也是個很好的例子嘿辟。電腦可以認為是一種通用數(shù)據(jù)操控機器。這個機器本來是幾乎啥也沒有的片效,沒有計算器红伦、沒有表格……而操作系統(tǒng)的設計者則先建立一層抽象,設計出各種接口淀衣,包裝起對硬件的操控昙读,并另外設計了一套“小動作”,可以更方便膨桥、更有邏輯地調(diào)用各種接口蛮浑。然后,操作系統(tǒng)的設計者再在這一層上建立各種模型——處理文檔的模型国撵、處理幻燈片的模型……其實陵吸,這就是所謂的軟件。
作為結尾介牙,需要提醒大家的是壮虫,編程者設計抽象的品味是大相徑庭的。一些商業(yè)上很成功的程序其實有著很壞的設計环础。如果想要學習編程囚似,那一定要嚴格地審視自己的和他人的程序,思考并評價其設計线得,并慢慢形成自己的“編程品味”饶唤。