2017-09-20

程式邏輯思考與虛擬碼(Pseudo Code)

學習電腦程式語言和寫程式,好像是件很難的事,但到底難在那?我們一起來探索看看



對還不會或正想學習電腦程式語言的人來說,要寫出一大串由英、數字和@#$%&這些奇怪符號組成的電腦程式,似乎是件令人望而生畏的事。但其實學寫電腦程式並不會比學英語、日語困難。不相信嗎?能寫CJavaJavascriptPython等電腦程式的人很多,但可不是每個能用英文寫出電腦程式的人都能用英語會話。但即使如此,學寫程式還是會讓人覺得很困難。我們來試著分析看看,到底困難的地方在那?
在開始之前,先來了解一下電腦程式是什麼?在這個人手一支甚至兩隻以上智慧型手機的時代,問什麼是電腦程式恐怕會被懷疑腦筋是不是有問題?
「手機裡的APP就是電腦程式呀,每天都在用的,還問這個?!」
是的,手機裡的APP就是電腦程式,沒錯!但那是完成編譯後的電腦程式,只能提供您操作,由電腦或手機執行。但我們要討論的是電腦程式的初始形態程式碼又稱原始碼(source code),這個部份才和我們要討論的寫程式有關。相信大家都對電腦程式有一定程度的了解,但我們還是先來看看電腦程式的定義:

A computer program is a collection of instructions that performs a specific task when executed by a computer.
電腦程式是一堆指令的集合,可以由電腦執行並完成特定的工作。

A computer program is usually written by a computer programmer in a programming language.
電腦程式通常由電腦程式設計師以程式語言(programming language)撰寫。

From the program in its human-readable form of source code, a compiler can derive machine code—a form consisting of instructions that the computer can directly execute.
電腦程式是以人類可閱讀的原始碼形態存在,然後由式編譯器(compiler)將其轉換成電腦可以直接執行的機械碼(machinecode)形式。

A program is a sequence of instructions that tell a computer how to do a task. When a computer follows the instructions in a program, we say it executes the program.
電腦程式是一連串的指令,可以告訴電腦如何進行工作。當電腦依循這些指令進行工作時,我們便說電腦在「執行」程式。


從以上的定義,我們可以簡單歸結為,「電腦程式是由程式設計師以程式語言撰寫的一連串指令,目的是告訴電腦要作什麼。」
這個簡單的定義對我們要討論的問題就很有意義了,此定義下所包含的動作「程式設計師以程式語言撰寫」叫作Programming,翻成中文是「程式設計」、「撰寫程式」,就是我們想討論的「寫程式」了。同樣的,我們還是確定一下Programming的定義是什麼?

Computer programming (often shortened to programming) is a process that leads from an original formulation of a computing problem to executable computer programs.
電腦程式設計(programming)是一個將待處理的問題由原始的表述轉換成電腦可執行的程式的過程。


這個是個很棒的定義,而且直指我們想討論問題的核心,「將問題由原始的表述轉換成電腦可執行的程式的過程。」
舉個例子,「我想列出我的智慧型手機通訊錄裡,所有生日在9月的人」,這是問題原始的表述,但怎麼進一步變成電腦程式呢?這個問題是否才是真正造成學習電腦程式語言困難的地方,就像下列這張圖呈現的:


「如何將問題原始的表述,變成電腦可以看得懂的程式?」

問題原始的表述是我們人類對問題的理解,但怎麼變成電腦程式?直接就可以轉換過去嗎?對資深的程式設計師或已經熟悉程式設計的人來說,或許是的。但對沒接觸過程式設計的人來說,我想應該不是如此。就像學英語時,老師都會告訴我們,要學好英文,就必須以英文思考。

When you speak English, you must think in English!

然而對英文不是母語的人來說,難的不就正是以英文思考(think in English)嗎?
程式設計我想也是如此,難的地方就是我們必須以電腦程式邏輯的方式思考。因此,我試著分析整理了程式設計的過程如下:

Programming Thinking Process

當有一個待處理的問題時,我們將會依照以下的過程進行電腦程式設計:
1.      用人類(自己)的思考方式去理解、了解這個問題
2.      將我們的理解改用電腦程式邏輯思考
3.      產出程式化的處理過程描述
4.      將此程式化的處理過程描述套用某種程式語言規則,如CJavaPython
5.      產生電腦程式

這個過程中真正困難的地方,應該是在「將我們的理解改用電腦程式邏輯思考」並且「產出程式化的處理過程描述」。「電腦程式邏輯思考」應該才是學習程式設計者的任督二脈,而「產出程式化的處理過程描述」就是虛擬碼(Pseudo code)的功用。
一旦我們能以「電腦程式邏輯思考」並「產出程式化的處理過程描述」,使用那種電腦程式語言(CJavaPythonJavascript)來「產生電腦程式」,就不是太大的問題了。電腦程式語言都有特定的規則和要求,而且差異並不太大,只是套用規則罷了。當您很熟悉「電腦程式邏輯思考」以及電腦程式語言規則後,您就會很熟練、無意識的完成這些過程,而像圖中那條虛線直接由「電腦程式邏輯思考」到產生電腦程式了。就像當您一旦熟練開車後(尤其是手排車),您就不需像剛學開車時,每次起步都要想一下剎車在那一腳、油門在那一邊、什麼時候要踩、要放離合器、何時要換檔,忙得手忙腳亂了。

接下來,我們就來探討「電腦程式邏輯思考」和「程式化的處理過程描述」。我個人的經驗認為「電腦程式邏輯思考」可以拆解成以下三個部份:
針對待處理的問題,
u  找出反覆進行的規律
u  將規律與重要的資料參數化
u  精簡可重複執行的描述

這三個部份結合前述的程式設計過程,整理成如下程式化邏輯思考的7個步驟:

1步:以人類(自己)的思考理解問題
2步:用自然語言重新描述這個問題
3步:找出反覆進行的規律
4步:將規律與重要的資料參數化
5步:以 Pseudo code 精簡可重複執行的描述
6步:將 Pseudo code 套用程式語言規則
7步:驗證、執行程式

我們用幾個簡單的例子來試試這7個步驟,最後再介紹什麼是虛擬碼(Pseudo code)

第一個例子:Fizz Buzz遊戲
這個遊戲是國外的老師教學生除法時使用的,1 ~ 100的數字,能被3整除的,學生要唸Fizz;能被5整除的,要唸Buzz;能同時被35整除的,當然就唸Fizz Buzz了;而不能被35整除的,就把數字唸出來。所以由1唸到100會變這樣
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz, Fizz, 37, 38, Fizz, Buzz, 41 ...

因為到100太多,所以只列到41。這個遊戲是不是可以交給電腦來完成?寫成一個電腦程式來完成這個遊戲?當然可以,不可以就不會拿來當例子啦!我們來試試怎麼用程式化邏輯思考的7個步驟來完成它。
1步:以人類(自己)的思考理解問題
1100的數字,判斷可不可以被35整除。

2步:用自然語言重新描述這個問題
1一直數到100,可以被3整除的數字改用Fizz呈現,可以被5整除的數字用Buzz呈現,可以同時被35整除的數字用Fizz  Buzz呈現,不能被35整除的數字直接把數字呈現出來。

3步:找出反覆進行的規律
這裡反覆進行的規律是什麼?是不是從1數到100,並對每個數字判斷可不可以被35整除,共有3個判斷,要重複作100次。

4步:將規律與重要的資料參數化
規律是不是就是從1100重複判斷100次?可以判斷100次,那可不可以判斷150次、200次甚至300次?當然可以,所以判斷次數就是我們的參數,就是由1到幾?(在電腦程式裡,習慣用i這個英文字代表這個重複執行的參數!)

5步:以 Pseudo code 精簡可重複執行的描述
接下來要用Pseudo code 精簡可重複執行的描述我們找到的規律和參數。什麼是虛擬碼(Pseudo code)?簡單的說,它是一種用接近自然語言描述的程式化處理過程,不用管程式語言的規則和語法要求,也沒有任何標準的語法規定,只要大家看得懂,就可以了。我們試著把前第1 ~ 4步的分析,用Pseudo code描述看看:
迴圈 i = 1 ~ 100 (一個由1100重複執行的迴圈,i就是那個數字,也就是我們的參數,程式將重複執行的動作稱為迴圈)
如果i可以被3整除則
                印出Fizz
如果i可以被5整除則
                印出Buzz
        如果i不能被3整除,也不能被5整除則
                印出 i (那個數字)
繼續下一個i
好了,這樣我們就完成了用Pseudo code 精簡可重複執行的描述。它是不是精簡,而且可重複執行?是吧!那就對了,中文當然也可以用來作Pseudo code描述,誰規定非得是英文不可。
等等,那可以被3整除又可以被5整除的Fizz Buzz呢?我們來驗證看看這3個判斷,如果i=15的時候,是不是會先符合被3整除印出Fizz,然後又符合被5整除印出BuzzFizz Buzz是不是就出現了。那如果i=35,是不是只會符合第1或第2個判斷印出FizzBuzz。若i=1,不能被35整除,那就符合第3個判斷,印出i的數字。所以是不是所有的狀況都符合了。
剛剛說可以判斷100次,那可不可以判斷150次、200次甚至300次?這時候您一定知道,只要把i = 1 ~ 100改成i = 1 ~ 200,就是數到200i = 1 ~ 300,就是數到300了。因為是可以變動的,所以被稱之為參數。

6步:將 Pseudo code 套用程式語言規則
接下來要將第5步已經完成的Pseudo code 精簡可重複執行的描述套用程式語言規則,我們套用Python程式語言來試試。
迴圈 i = 1 ~ 100                                                             for i in range(1,101):
如果i可以被3整除則…                                              if i % 3 == 0:
                印出Fizz                                                                         print("Fizz")
如果i可以被5整除則…                                              if i % 5 == 0:
                印出Buzz                                                                        print("Buzz")
        如果i不能被3整除,也不能被5整除則…              if i % 3 != 0 and i % 5 != 0:
                印出 i (那個數字)                                                          print(str(i))
繼續下一個
真的就只有這樣,Python版的Fizz Buzz遊戲就寫好了,可以套用Python,當然也可以套用JavaC的程式語言規則,就變成Java程式或C程式了。

7步:驗證、執行程式
最後要確認這個Python程式是可以執行的,為了輸出結果的美觀以及不要為了控制格式而過度讓程式變複雜,我們多加了一點東西,但大致和剛剛的程式是相同的。

# 印出FizzBuzz數列
output = ""
for i in range(1,101):
 if len(output) > 0:
  output += ","
 if i % 3 == 0:
  output += "Fizz"
 if i % 5 == 0:
  output += "Buzz"
 if i % 3 != 0 and i % 5 != 0:
  output += str(i)
print(output)


您可以用我們在《佛心來著的線上編譯器 - Ideone.com》中介紹過的線上編譯器-Ideone.com,複製上面的程式碼貼上,來驗證這個程式。請注意Python是以縮排來控制程式區段,因此上述程式的縮排必須完全相同,不能有改變,不然執行就會出現錯誤


我們再試試第二個例子:
請以 Pseudo code 完成以下程式:
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
sum=(1+2+3+4+5)

1步:以人類(自己)的思考理解問題
一個由1開始的三角數列,每行遞增一個數字,最後輸出數字總和。

2步:用自然語言重新描述這個問題
一個由1開始一直到5的三角數列,第1行是1,第2行是1 2,第3行是1 2 3,第4行是1 2 3 4,第5行是1 2 3 4 5,第5行完成再將1 ~ 5加總印出總和。

3步:找出反覆進行的規律
這裡反覆進行的規律是什麼?是不是每一行都一定從1開始,然後第幾行就由1到幾(這個幾和行數是一樣的)。第1行是1 ~ 1,第2行是1 ~ 2,第3行是1 ~ 3,第4行是1 ~ 4,第5行是1 ~ 5

4步:將規律與重要的資料參數化
有沒有發現到這裡有2個規律在重複,第1個是第幾行(參數i),第2個是1到幾(參數j)

5步:以 Pseudo code 精簡可重複執行的描述
迴圈 i = 1 ~ 5  (第幾行)
        迴圈 j = 1 ~ i  (1到幾)
                印出 j
        繼續下一個 j
        印出 換行   (換新的一行)
        i加總存入總和     (1+2+3+4+5)
        如果 i = 5
                印出總和
繼續下一個 i
這樣就完成了,您可以試試讓i1 ~ 5,是不是輸出的結果會和例子相同。

6步:將 Pseudo code 套用程式語言規則
一樣套用Python程式語言規則
迴圈 i = 1 ~ 5  (第幾行)                               for i in range(1,6):
        迴圈 j = 1 ~ i  (1到幾)                                 for j in range(1,i+1):
                印出 j                                                             print(j,end='')
        繼續下一個 j
        印出 換行   (換新的一行)                          print()
        i加總存入總和     (1+2+3+4+5)             sum += i
        如果 i = 5 …                                           if i == 5:
                印出總和                                                        print("sum =",sum)
繼續下一個 i
在這個例子因為有一個加總,所以需要多加一個總和(sum)的變數,用來記錄加總的值。print(j,end='')的意思是,印出j但不換行。print()就是換行了。

7步:驗證、執行程式
可執行的Python程式會變這樣,多加了一個變數,您同樣可以複製下列程式碼貼上,來驗證這個程式。

# 印出三角數列
sum=0
for i in range(1,6):
 for j in range(1,i+1):
  print(j,end='')
 print()
 sum += i
 if i == 5:
  print("sum =",sum)


這裡可能有的人會想,是不是可以不用到2個迴圈,用1個迴圈就好了?事實上是可以的,這個思考技巧稱為縮減程式複雜度,超過本篇討論的範圍,但有興趣的人可以自己試試看。

最後再試一個簡單的例子,看看您是否真的都了解了?
請以 Pseudo code 完成以下程式:
*
**
***
****
*****
******
*******

1步:以人類(自己)的思考理解問題
以星號完成一個遞增的直角三角形,從第11顆星一直到第77顆星。

2步:用自然語言重新描述這個問題
1行印出1顆星,第2行印出2顆星,第3行印出3顆星,第4行印出4顆星,第5行印出5顆星,第6行印出6顆星,第7行印出7顆星。

3步:找出反覆進行的規律
每一行印出與該行數相同的星數,由第1行反覆進行到第7行。

4步:將規律與重要的資料參數化
您不難發現,這裡的規律包含了第幾(參數i)行,重複列出幾顆(參數j)星。同樣的,可以印7行,是不是也可以印9行、10行、100行?所以這就是我們的參數。

5步:以 Pseudo code 精簡可重複執行的描述
跟前一個例子很像,但更簡單了。
迴圈 i = 1 ~ 7  (第幾行)
        迴圈 j = 1 ~ i  (1到幾顆星)
                印出 *   (每一行的星星的相連的,不換行)
        繼續下一個 j
        印出 換行   (換新的一行)
繼續下一個 i

6步:將 Pseudo code 套用程式語言規則
迴圈 i = 1 ~ 7  (第幾行)                       for i in range(1,8):
        迴圈 j = 1 ~ i  (1到幾顆星)                 for j in range(1,i+1):
                印出 *                                                    print("*",end='')
        繼續下一個 j
        印出 換行   (換新的一行)                  print()
繼續下一個 i

7步:驗證、執行程式
這個例子的程式沒有加總,也不用多作別的控制,複製下列程式碼貼上,來驗證這個程式。

# 印出星號三角形
for i in range(1,8):
 for j in range(1,i+1):
  print("*",end='')
 print()


您可以試試看這7個步驟是否有助於您建立程式邏輯思考。

最後介紹一下Pseudo code (虛擬碼),以下是它的定義:
An informal high-level description of the operating principle of a computer program or an algorithm.
以非正式且接近人類口語的方式描述電腦程式或演算法的運作原則。

It can be used to explain any programs or algorithms.
虛擬碼可以被用來解釋、說明任何程式或演算法。

It generally does not actually obey the syntax rules of any particular language.
虛擬碼不需遵從任何特定電腦程式語言的語法規則。

There is no systematic standard form.
虛擬碼沒有標準的形式。

You can use natural language descriptions.
可以使用自然語言描述。

簡單的說,Pseudo code (虛擬碼)是讓不同電腦程式語言的程式設計師可以用來溝通程式的一種非正式、接近自然語言的工具。程式設計師可能熟悉不同的電腦程式語言,有的熟悉C語言,有的是Java,有的是Python,那不同的語言的程式設計師怎麼溝通程式呢?Pseudo code (虛擬碼)就是這個答案,可以讓使用不同語言的程式設計師,輕鬆的一同討論程式,而不需決定要用誰的電腦語言。同時,也可以用來描述程式化的處理過程,而不需有任何電腦程式語言經驗


參考資料: