Matlab 教材:多值函式

就像數學有「向量值函數」,Matlab 也有「多值函式」。 更厲害的是,Matlab 可以檢查使用者想要幾個答案? 他可以根據使用者的不同要求而回答不同類型的答案。 這是 Matlab 語言比其他大多數程式語言 (例如 C 語言) 方便的地方。

例如當 A 是一個 3x4 的矩陣,則函式

p=size(A)
傳回的答案是個 2 維序列,序列的第一個元素是 A 的列數, 第二個元素是 A 的行數;也就是
p = [3, 4]

Matlab 本來就是個陣列導向計算軟體,所以 Matlab 能夠得到序列或向量的答案並不稀奇。 有趣的是,您也可以要求 size() 傳回兩個變數,有如

[m,n] = size(A)
mn 分別是一個純量: mA 的列數,而 nA 的行數;也就是
p = [m, n]

可見 size() 被要求傳回一個變數 (p=size(A)) 或者被要求傳回兩個變數 ([m,n]=size(A)) 這兩種情況, 雖然回答的答案一樣,但是回答的方式不同。 這就是我所謂 Matlab 可以「根據使用者的不同要求而回答不同類型的答案」的意思。

再看一個例子。令 x 是一個序列,例如

x = [2 0 7 4 9 5];
那麼,如果只要求一個回傳變數,則 sort() 內建函式就把 x 從小到大排序,例如
sorted = sort(x)
得到 sorted[0 2 4 5 7 9]。 順便提兩個要點:

如果向 sort() 索取兩個回傳變數,則 sort() 的行為就有點改變。 例如

[p, q] = sort(x)
p 就相當於剛才的 sorted,也就是 [0 2 4 5 7 9]。 而 q 就是原來 x 元素之足標被排序之後的新順序,也就是 [2 1 4 6 3 5], 留意看看,q 的元素數值是從 1 到 6 的整數,而且每個整數出現一次。 它的意思是說,x 排序之後的結果,是
[0 2 4 5 7 9] == [x(2), x(1), x(4), x(6), x(3), x(5)]
換句話說,如果說
[sorted, ind] = sort(x);
x(ind) 的重新排列結果就是 sorted。 簡單地說,sorted 給我們排序的結果, 而 ind 保留了排序前的足標號碼。

獲得了 ind 有許多好處。譬如現在 A 是一個代表成績簿的矩陣, 它的第一行代表學號,而後每一行代表一次作業或考試的成績。 假如第 7 行是期中考成績,那麼

[sorted, ind] = sort(A(:,7));
sorted = sorted(end:-1:1);
ind = ind(end:-1:1);
其中 sorted 告訴我們期中考成績的排序,譬如最高分是 sorted(1) 而最低分是 sorted(end)。 而 ind 可以告訴我們,是「誰」得了最高分、「誰」得了最低分。 那就要用到學號那一行,所以 A(ind(1),1) 是得到最高分那人的學號, 或者 A(ind(7),1) 是排名第七的人的學號。

使用者自訂函式也可以檢查 caller 那端索取幾個回傳變數, 並據以改變本函式的行為。重點是 Matlab 的另一個內建函式 nargout() (number of output arguments)。 我們繼續上一節的 collatz.m,再修改它, 讓使用者可以索取一個或兩個回傳變數。 如果只索取一個,那就跟以前寫的一樣:collatz(x) 回傳以 x 為首的 Collatz 數列長度。 如果索取兩個,則第一個變數是數列長度, 第二個變數就是以 x 為首、直到第一個 1 為止的 Collatz 數列, 存在一個序列裡面。

整個原始碼寫在這裡,其基礎是前一節的原始碼。 為了方便閱讀,我們將新加入的原始碼用藍色字型顯示。

function [n, z] = collatz(x, arg2)
% Usage: [n, z] = collatz(x, [arg2]) or n = collatz(x, [arg2])
%
% Here x is a positive integer, let it be x_0 and generate the
% Collatz sequence until the first n such that x_n = 1.  Return n.
% arg2, which is optional, specifies the max number of iterations.
% The second return variable z, which is optional, is the actual
% Collatz sequence computed to the first 1 or to the
% end of iterations.
%
% Shann 2004-05-05

% Check input arguments
if (nargin == 2)
    ITER = arg2;
elseif (nargin == 1)
    ITER = 1000;
else
    disp(['Error: must have one or two input arguments.']);
    return;
end

% Input validation: is a scalar, is an integer, is positive.
if (length(x) > 1)
    disp(['Error: input must be a scalar.']);
    return;
end
if (floor(x) ~= x)
    disp(['Error: input must be an integer.']);
    return;
end
if (x < 1)
    disp(['Error: input must be a positive integer.']);
    return;
end

% Let x be x_0, start the Collatz sequence x_n, n=1,2,3...
% until the first x_n = 1
n=0;
if (nargout==2)
    z = x;
end
while (x>1) & (n<ITER)
    n = n+1;
    if (rem(x,2))
        x = 3*x+1;
    else
        x = x/2;
    end
    if (nargout==2)
	z = [z, x];
    end
end

以上我們用了一個 Matlab 特有的技巧:在既有的序列後面添加元素的技巧。 如果使用者要求兩個回傳變數,collatz() 才著手準備 z 序列; 否則根本不產生 z 序列。 一開始的時候,z 並不是序列,它只是一個純量 x0。 但是,Matlab 其實將純量視為 1 乘 1 矩陣,同時也是 1 維序列、1 維向量。 然後,在迴圈內,我們用

z = [z, x];
這個技巧,讓新計算出來的 xn 接續到現有的 z 序列後面。 譬如若 z 原來是 [3 10 5],而新計算出來的 x 是 16,那麼 [z, x]; 合併起來就是 [3 10 5 16]。 這是 [矩陣的合併] 那一節講解的技巧。

使用新版的 collatz(),如果說

collatz(3)
或者
n = collatz(3, 100)
都還是得到答案 7,但是現在可以看看以 3 為首的 Collatz 數列究竟如何:
[n,x] = collatz(3, 100);
x[3 10 5 16 8 4 2 1]。 萬一設定的迭代次數不夠,例如
[n,x] = collatz(3, 5);
則只會算到 x5 為止, 此時 x 有六項,是 [3 10 5 16 8 4]

Matlab 有個壞習慣:它一定要序列的足標從 1 開始。所以,collatz()z(1) 存著的其實是 x0, 依此類推,z(k) 其實儲存 xk-1。 這是使用 Matlab 的一個不方便的地方。

習題

  1. 參照第A講提到的 [虛擬成績表格], 設法將學號和第二次段考成績 (M2) 輸入 Matlab, 列出第二次前 15 名的學號。
  2. 如果現在要獲得 1, 2, 3, ..., 100 這一百個整數的「亂排」, 該怎麼做?(所謂亂排是紊亂而找不到明顯規則的排序,每個整數恰出現一次, 不能遺漏。)
  3. 令 x0=3,4,...,1000, 找到這些 x0 所對應的 Collatz 數列最大值。 例如當 x0=3,對應的最大值是 16。 寫出來每個 x0 對應的最大值, 並統計總共有幾個不同的最大值、每個值發生的頻率。
[BCC16-B]
單維彰 (2004/05/05) ---
[Prev] [Next] [Up]