演算法

概論

在題目中有個部分稱作演算法 (Algorithm),可以用來儲存可被 Maple T.A. 理解的程式碼。這些程式會在答題者打開題目時執行,執行後再生成題目。所以在題目生成以後,演算法就不會再執行。所有題目類型中,只有 Maple-graded 題目有可能在答題後呼叫 Maple 程式評分,除此之外就不會有其他執行 Maple 或 Maple T.A. 程式的時機了。

使用演算法通常有三個目的,第一個目的是產生可變動的題目。利用演算法,每次答題者打開題目時,看到的題目都會有變動,無法用背答案的方式作答。如果用在較為正式的作業或考試,則每位學生的題目可能都不一樣,學生也較不會作弊。如果是較用功的學生,也可利用這項功能重複練習同一種類型的題目,加深學習印象。

使用演算法的第二個目的是借用 Maple 的超強功能。Maple 是非常強大的數學軟體,如果是熟練的使用者,可以利用這些功能來達成一般來說不可能自動評分的題目。更進一步說,如果不能借用 Maple,要生成高水準的可變動題目將會非常麻煩,對出題者的程式和數學功力要求會很高。

演算法的第三個用途是展示數學式。雖然 Maple T.A. 有提供數學式編輯器編寫數學式,但對於較老練的使用者而言,這種編寫方式有很多缺陷。如果透過演算法,可以用指令的方式生成數學式圖片並插入文件當中。如果是亂數生成的函數,就更有必要透過演算法變成較美觀的數學式。

語法規則

請看底下這句話:

$foo=maple("randomize():rand()");
這句話是演算法的一個範例。演算法中的每一列程式結構都跟這句話一樣,開頭是一個變數放在等號左邊,等號右邊是函數或是算式,句尾要有一個分號 (;)。Maple T.A. 提供的演算法並不是很正式的程式語言,每一列程式碼開頭都必須有一個變數來紀錄程式結果,而且 Maple T.A. 的每個函數一定都會回傳一個運算結果。Maple T.A. 有提供用來代表流程控制的函數,但是沒有迴圈,所以就一個程式語言來說,Maple T.A. 是非常容易學習的。但是如同上面那句話,Maple T.A. 提供 maple() 函數讓出題者可呼叫 Maple kernel,裡頭用的是 Maple 語法,其功能和複雜度就高得多了。

變數

演算法要做的事就是生成變數並在變數中存入適合的內容,然後把變數放入題目的各個部分,如題目敘述、註解或是答案欄,Maple T.A. 會把變數轉成變數所代表的內容。演算法並沒有類似程式語言輸出輸入功能,所謂輸出,就只是把特定內容放入變數,再把變數放到題目或答案中。而唯一能解讀答題者的輸入只有 Maple-graded 題目,但這功能就不是演算法提供的。所以不管在演算法中做過哪些計算,只要沒有將結果存入變數,這些計算就沒有任何意義。

Maple T.A. 變數命名規則跟 PHP 類似,開頭是「$」,然後跟著英文字母和數字。例如:

$num=1;
這段程式碼中 $num 就是一個變數,程式碼的目的是將數字 1 存入 $num 中。因此在之後的程式碼或是題目敘述等地方,$num 會被 Maple T.A. 視為數字 1。

變數在使用前不用事先宣告,想要儲存數字或文字,直接指定就可以了:

$a=3;
但是文字必須要用單引號或雙引號包起來:
$a='string';
變數不僅存放數字或文字,也可存放數學式或圖片,這些式子和圖片必須透過 Maple T.A. 提供的函數生成。另外,雖然不知道 Maple T.A. 用哪種方式儲存數學式,但目前 Maple T.A. 顯示數學式時一定會以圖片方式呈現,所以在使用上數學式跟圖片是一樣的。

此外,Maple T.A. 有兩個內建常數─e 和 pi,e 約等於 2.71828,pi 則是圓週率。常數不用加 $ 開頭,所以

$a=e;
$a 大約是 2.71828。另外一個可用在指定變數的語法是科學記號表示法,例如
$a=2.9E8;
$a 會變成 290000000。

運算與數學函數

Maple T.A. 可執行四則運算和次方 (^) 運算,例如:

$a=2+3*5^2;
運算後 $a 等於 77。算子優先順序依序是次方、乘除、加減,如果要改變優先順序,可以用括號把希望優先計算的部分包起來。

除了四則運算,Maple T.A. 也提供常見的數學函數:

函數回傳結果範例
abs(x)x 的絕對值abs(-3)=2
sqrt(x)x 的平方根sqrt(2)=1.414...
ln(x)x 以 e 為底的對數ln(e)=1
log(x)x 以 10 為底的對數log(10)=1
exp(x)e 的 x 次方exp(1)=e
fact(x)x!fact(4)=24
gcd(x,y)x 和 y 的最大公因數gcd(4,6)=2
frac(x,y)x/y 分子分母型式,MathML 字串

輸出數學式

請注意上一節介紹的最後一個函數 frac(),這個函數並不是數學函數,不執行任何數學運算。frac() 的用途是將 x/y 變成分子在上分母在下這種較好看的表達形式。這種表達式據說是以 MathML 字串儲存,但其實 Maple T.A. 會將這種字串轉換成圖片,所以存在變數中的就是一張圖片。

在繼續討論前,先簡述 MathML。MathML 是 W3C 提出的標準,指出如何在 XML 文件中顯示數學式。透過 MathML,網頁中也能呈現過去只有在 TeX 等文件才能呈現的數學式。Maple T.A. 題目敘述不接受 MathML 語法,但如果將 MathML 語法放到變數中,Maple T.A. 會將其轉換成圖片後呈現。另外,用 Equation Edit 產生的數學式雖然是以圖片呈現,但要是將題目還原成最原始的樣子,就會發現這些數學式也是用 MathML 編寫的。

MathML 表達法分為兩類,一類是意義表達,一類是圖象表達。圖象表達跟 TeX 一樣,只要將數學式寫出來就好;而意義表達必須將每個符號意義都說得清清楚楚,因此意義表達的數學式將會非常冗長。意義表達的好處是用這種方式寫出來的數學式可以很容易送入其它程式中解讀,所以各種後處理 (如轉換成語音等) 都非常容易。不過一般來說意義表達實在太過麻煩,所以圖像表達法還是比較常用。

要自己動手寫 MathML 還是很困難,所以還是利用轉換函數比較實際。其中一個辦法是呼叫 mathml() 函數:

$bar=mathml("1+2");
mathml() 會接受一個字串 (用單引號或雙引號包起來),並嘗試解讀字串後轉換成數學式。如果想要建立更複雜 (如包含積分符號) 的式子,就必須呼叫 Maple 提供的 MathML() 函數。

呼叫 Maple kernel

底下是呼叫 Maple kernel 的例子:

$bar=maple("1+2;2*3");
呼叫 Maple kernel 的函數是 maple(),引數是字串─合法的 Maple 程式碼。透過 maple() 呼叫 Maple kernel 時,無法用換列字元分開程式碼,但只要每個指令結尾有加分號,程式就可以正確執行。maple() 傳回去的值是送入的程式碼的最後一個輸出結果,上面這個例子有兩個指令,第一個結果是 3,第二個結果是 6,因為第二個結果是最後一個結果,所以 $bar 等於 6。

如同上一節提到的,Maple 也提供建立 MathML 式子的函數,函數的名字就是 MathML()。底下是一個例子:

$ML=maple("MathML[ExportPresentation](a+b)");
實際上,MathML 並不是函數,而是一個 Maple package,而 MathML[ExportPresentation]() 才是函數,意思是將輸入的數學式轉換成圖象表達的 MathML 語法 (ExportPresentation)。這個指令會在變數 $ML 存入底下這個句子:
"<math xmlns='http://www.w3.org/1998/Math/MathML'><mrow><mi>a</mi><mo>+</mo><mi>b</mi></mrow></math>"
當然 Maple T.A. 顯示的是這個句子所代表的圖片。送到函數中的數學式只要是 Maple 能接受的數學式就可以了,所以就算是放入像 int()(積分函數) 這類指令,Maple 也能正確的印出積分符號、寫出上下界並給予合適的空間,就這點來說會比 Maple T.A. 的 mathml() 來得方便。但是 Maple 並不是專門排版的軟體,所以還是有缺陷,底下是一個例子:
$ML2=maple("MathML[ExportPresentation](3+5)");
或許打下這的指令的人希望 $ML2 存放的是 3+5,但是實際上存放的是 8─Maple 會將能算的都算完後再把不能算的轉換成 MathML。

流程控制

Maple T.A. 沒有像 if...else 這種設計,但還是提供了類似的解決方案。底下指令

$un=if($a,20,30);
如果 $a 不等於 0,則 $un 是 20,反之 $un 等於 30。

要使用流程控制函數,比較函數就很重要了。Maple T.A. 提供下列比較函數:

函數功用
eq($a,$b)$a 等於 $b 回傳 1,否則回傳 0。
ne($a,$b)$a 不等於 $b 回傳 1,否則回傳 0。
gt($a,$b)$a 大於 $b 回傳 1,否則回傳 0。
lt($a,$b)$a 小於 $b 回傳 1,否則回傳 0。
not($a)$a 等於 0 回傳 1,否則回傳 0。

亂數產生器

要編寫出有變化的題目,需要利用亂數產生器。Maple T.A. 提供幾個亂數產生器:

函數功用
rint($n)回傳介於 0 和 $n-1 的任一整數。
rint($m,$n)回傳介於 $m 和 $n-1 的任一整數。
rint($m,$n,$k)回傳 $m、$m+$k、$m+2*$k‧‧‧$n-$k 中的任一整數。
range($n)回傳介於 0 和 $n 的任一整數。
range($m,$n)回傳介於 $m 和 $n 的任一整數。
range($m,$n,$k)回傳 $m、$m+$k、$m+2*$k‧‧‧$n 中的任一整數。
rand($m,$n)回傳介於 $m 和 $n 的浮點數。

Maple T.A. 提供了 switch() 函數,用法範例如下:

$ans=switch($num,'sin','cos','tan','cot','sec','csc');
如果 $num 是 0,則 $ans 會收到字串 sin,如果 $num 是 1,則 $ans 會收到字串 cos,依此類推。通常 switch() 會和 rint() 合併使用,比如
$ans=switch(rint(6),'sin','cos','tan','cot','sec','csc');
能在六個三角函數中隨機選出一個。這些字串也可以接受中文,筆者看過最無聊的隨機題目只是讓題目中的人名在小明、小強、小華等幾個名字中任選一個‧‧‧。

除了 Maple T.A. 提供的亂數產生器,出題者也可透過 maple() 呼叫 Maple 的亂數函數,未來在其他網頁會介紹其中幾個亂數函數。由於 Maple 提供的亂數函數功能非常強大,所以對熟悉 Maple 的出題者來說,用 Maple 的亂數函數會比用 Maple T.A. 的亂數產生器來得方便。

condition

cnodition 是 Maple T.A. 唯一一個特殊的指令,這個指令不會產生回傳值,不像 if() 這種假的流程控制,condition 是真正的流程控制 (雖然和一般的流程控制截然不同)。雖然實作細節不明,condition 的用途是確保隨機產生的函數不會發生某些情況,例如兩個變數不相等或是某個變數不等於 0。

底下是一個範例:

$num1=rint(10);
$num2=rint(10);
condition:ne($num1,$num2);
如果在這段程式碼後沒有再改變 $num1 和 $num2 的值,則 $num1 和 $num2 可能是從 0 到 9 中的任意一個數字,但是這兩個變數絕不可能相等,因為第三列程式會排除這個事件。Condition 後面有一個冒號 (:),再後面跟著一個比較函數。如果比較函數傳回 1,則條件通過,程式繼續執行。也就是說只有 Condition: 後的條件滿足,程式才能繼續執行。如果出題者寫了一個永遠不可能滿足的條件,則儲存題目時 Maple T.A. 就會提出警告。

畫圖

在呼叫 maple() 時,回傳的都是文字,即使是用 MathML 產生的數學式,其本質還是文字。Maple T.A. 提供的 plotmaple() 指令雖然同樣呼叫 Maple kernel,但是可以輸入 Maple 畫圖指令,並將得到的圖片存入變數之中。底下是一個範例:

$plot1=plotmaple("plot(sin(x), x=-10..10)");
這句話透過 plotmaple() 呼叫 Maple 的 plot() 函數,可以畫出 2D 圖片。所以 $plot1 存放的就是圖片,內容是三角函數 sin(x) 從 -10 到 10 的曲線。

再看底下這個範例:

$myplot=plotmaple("plot(sin(x), x=-Pi..Pi), plotdevice='gif', plotoptions='height=250, width=250'");
這個例子中有三個參數,plotdevice、height 和 width。height 和 width 可指定圖片的大小,而 plotdevice 指定圖片的格式是 gif 或 jpeg。如果是靜態圖片,使用 jpeg 就可以了,如果是動態圖片,則要用 gif。Maple 可以生成動態圖片,但是筆者從未試過,所以不知道實際上是否真的能用在題目上。要使用這條指令,建議從這裡複製過去再修改必要的參數。由於一些奇怪的 BUG,目前確定參數中的等號前後加上空白時可能會導致參數失效。

由於演算法只在題目生成以前執行,所以無法寫出可讓答題者在答題前先檢查自己答案的圖形的方法。但在 Maple-graded 問題可以在答題後畫圖,讓答題者知道自己的答案會產生什麼圖形。詳細情況請自行研究。

其它

下表列出一些之前沒提過的函數:
函數功用範例結果
int($num)取出浮點數 $num 的整數部分。int(12.34)12
decimal($n,$num)浮點 $num 四捨五入到小數點下 $n 位。decimal(3,12.3456789)12.346
sig($n,$num)浮點 $num 四捨五入到前 $n 位。sig(3,12.3456789)12.3
lsu($n,$num)指出 $num 第 $n 位數的基本單位。lsu(3,12.3456789)0.1
min($n1,$n2,$n3,$n4,...)取最小值。min(2,3,-1,-2,5,9)-2
max($n1,$n2,$n3,$n4,...)取最大值。max(2,3,-1,-2,5,9)9
indexof($k,$n1,$n2,$n3,$n4,...)找出序列中第一個 $k 的位置,如果沒有則傳回 -1。indexof(2,3,-1,2,5,-9,2,3,-2)2
strcat($s1,$s2,$s3,$s4,...)將字串組合起來。strcat('一','二','三四')'一二三四'
除此之外還有幾個函數沒有在此說明,但可在官方提供的說明手冊中找到。這些函數大部分都可以透過 maple() 來達到相同的效果,所以就省略了。只有 java() 函數可以用來呼叫出題者的 Java 程式,但由於這已經遠遠超過這份文件能支援的範圍了,所以請有需要的讀者自行閱讀說明文件。


上一個主題:建立題目

下一個主題:範例和常用函數

首頁