本文出處: →教學成果←

在學校裡我上過的程式課只要上到迴圈,當次的作業一定就是印星星,用 * 排列成各種圖形。

在這種作業裡面就可以瞭解對迴圈的流程與控制的程度了。

舉例來說:

type A 

*
**
***

1
12
123 

type B 

    *
  **
***

    3
  23
123 

很明顯的 type A的比較好寫,type B的就要想一下

迴圈的初值是多少,迴圈要遞增還是遞減,空白跟斷行要怎麼處理。  

所以說~迴圈的控制把這邊弄清楚就差不多了。


許多人寫程式是先從印三角形,印正方形一類的東西開始的。給定一個 n, 程式必須印出高度是 n 的三角形:

*

***

*****
:

*************

|--------- n --------|

同 學們現在回想起這個程式,應該覺得再簡單不過了。即使是這樣簡單 的題目,在我們動手寫程式前仍然可以先作一些簡單的分析。一個高度 是 n 的三角形自然有 n 行,每一行都是先印一些空白,製造空間,然 後印幾個星星。最後別忘了換行,以便印下一行的工作能夠繼續。第一 行,三角形的頂端,只有一個星星。但是在印星星之前,需要印幾個空 白,把星星給推到中間的位置。幾個空白呢?應該是n-1個。


......n-1.........*
......n-2.......***
......n-3......*****
......n-4...*******      :

同樣的,第二行印三個星星。但是在三個星星之前要先印n-2個空白。 第三行五顆星星,n-3個空白。依此類推,

--- 第i行需要 n-i 個空白,緊接著2i-1顆星星,再緊接著一個換行

我 們現在可以動手寫程式了。程式真正核心的部份可以用分層的方式來 瞭解。由於三角形有 n 行, 因此我們需要有一個迴圈,把印每一行的 動作重複 n 次。 迴圈內寫的就是一段通用的,用來印每一行的程式。 而每一行都有一些空白,一些星星,所以裡面又靠兩個獨立的迴圈來印 空白,和星星,最後換行。


for i:=1 to n do { 三角形有n行 }
begin
for j:=1 to n-i do
write (" "); { 先印空白 }
for j:=1 to 2*i-1 do
write ("*"); { 印星星 }
writeln; { 然後換行 }
end;

但是既然整個好多層的迴圈裡面都是在做 write 的動作, 我們不妨突 發奇想一下,可不可以把write給「提出來」?

如果有這麼一個語言, 可以把write擺外面:


write (
for i:=1 to n do
begin
for j:=1 to n-i do " " { 空白 }
for j:=1 to 2*i-1 do "*" { 星星 }
" " { 換行 }
end
);

依照C語言的習慣,用" "表示換行。

For 是用來計數的迴圈。但是再仔細觀察,在 for j:= .. to .. do "*" 中,我們只是重複的產生"*", 每印出一個"*"的動作並不因 j 的值而有所不 同。相對的,對外層的 for i:= .... 而言,每一個不同的 i 值,就會印出 截然不同的一行來。基本上,外面的for i:= 和裡面的 for j:= 雖然都使用 for 來寫,但其實是性質不同的兩種迴圈。

事實上, for 這個迴圈對這個程式來說仍然太一般性了,對裡面的兩個迴圈 而言,For loop在這裡有些大才小用,「say more than what you mean」。 也許我們可以專門設計一個語法,表示「一模一樣的動作,重複 n 次」。

既然是為了要印字元,我們就仿造 formal language 裡面的作法,借用乘冪 的符號 ^ 來表示重複那個字元。


write (
for i:=1 to n do
begin
" " ^ (n-i);
"*" ^ (2*i-1);
" ";
end;
);

看到了嗎?程式可以讀成:印出(write)以下的東西,一共n行(for i:=1 to n), 其中第i行有n-i個空白,2i-1個星星,最後接著一個換行。

而連著三行的部份,其實可以併成一行。只要再定義一下:"."表示連接 (同樣借用 formal language 中表示字串用的符號)。


write (
for i:=1 to n
" "^(n-i) . "*"^(2*i-1) . " ";
);

於 是我們很明顯的看出來,這個程式是在印東西(write), 共印出 n 行, 每行有n-i個空白(" "^n-i), 緊接(.)著2*i-1個星星("*"^(2*i-1)), 然後 再緊接(.)著換行(" ")。這也幾乎就是我們最初的時候,所寫下的對題目 的分析了。

一個合乎題目要求的三角形, 共有n行, 其中第i行需要 n-i 個空白,緊接著2i-1顆星星,再緊接著一個換行

* * *

不妨再回顧一下我們最初的程式


for i:=1 to n do
begin
for j:=1 to n-i do
write (" ");
for j:=1 to 2*i-1 do
write ("*");
writeln;
end;

這兩種程式寫作的方法差在哪裡?

最 初的程式告訴電腦的是「如何(how)」印出一個題目所要求的三角形。首 先用一個for loop告訴電腦,下面的動作要作 n 遍;在這n遍之中,每一遍又 要把「write(" ")」這個動作作 n-i 遍,把「write("*")」這個動作作 2*i-1 遍。最後印一個換行。

而最後的程式告訴電腦的是一個合乎程式要求的三角形「是什麼(what)」。 它告訴電腦,一個這樣的圖形,是由 n 個單位所組成的,每一個單位有 n-i個" ", 2*i-1個"*", 1個" "。最後要電腦把這個圖形給印出來。

告 訴電腦「how」的,稱作 imperative programming,「指令式」的程式設計。 這樣的觀念根源在於,假定我們餵給電腦的是一連串的指令,告訴電腦先作 這個,後作那個。我們通常用的程式語言,如C, Pascal, BASIC等等,都是 屬於這個種類。然而有些時候,我們發現這樣的程式設計方法過於繁瑣。

而告訴電腦「what」的,則稱作 declarative programming,「宣告式」的程 式設計。Prolog 這類邏輯語言,和 Haskell, ML 這類函數語言,都傾向於宣 告式的。

也許您並不會很驚訝地發現,在 Haskell 中,印同樣的三角形是這樣寫的:


triangle n = (concat . map (line n)) [1..n]

-- 產生 1..n 行,--並連接( concat)起來

line n i = repeat (n-i) " "

-- 每行有 n-i 個空白 ++ repeat (2*i-1) "*" ++ " " -- 和一個換行

-- 2i-1 個星星

 

arrow
arrow
    全站熱搜

    正義的胖虎 發表在 痞客邦 留言(0) 人氣()