2007年3月22日木曜日

matlab: セル配列の参照法とその周辺


※本記事を大幅に拡充して「matlab: セル配列で困っている人へ」を執筆しました.2013.06.10

セル配列は Matlab の配列の中でもやや異色である.
最も大きな特徴は,他の任意の型の変数(配列)を要素として扱えることだろうが,
もう一つ,中括弧がセル配列専用の参照演算子 {} として用意されているのも独特だ.
しかも,通常の参照演算子である小括弧 () でもアクセスできる.
そして,小括弧でアクセスした場合と中括弧でアクセスした場合には結果が異なるのである.
根本的には,小括弧は「セル配列の部分配列を取り出す」,中括弧は「要素セルに格納された中身を取り出す」と考えられる.
試してみよう.まず,セル配列を作る.
>> a={1,2};
次に,小括弧で普通にアクセスしてみる.
>> a(1)
ans =
    [1]
このとき,a(1) 自体はセル配列である.つまり,b=a(1) とすれば,b は 1 by 1 のセル配列となる.
さらに,インデクスリストでアクセスしてみる.
>> a(:)
ans =
    [1]
    [2]
ここでももちろん a(:) 自体はセル配列である.つまり,b=a(:) とすれば,b は 2 by 1 のセル配列となる.
では,中括弧でアクセスしてみよう.
>> a{1}
ans =
     1
このとき,a{1} は数値である.さらに,インデクスリストでアクセスしてみる.ここからが面白いわけですよ.
>> a{:}
ans =
     1
ans =
     2
なんと ans が 2 つ.つまり 2 つのセンテンスが評価されたことがわかる.
中括弧使用時のインデクスリストによるアクセスとは,一見単独の文に見えながら,複数の参照文をシリアルに実行しているのに他ならないのだ.
ということは,である.当然,この参照結果を一つの変数にそのまま代入することはできない.
>> b=a{:}
??? Illegal right hand side in assignment. Too many elements.
ただし b=a{1} のような単一要素の参照ならもちろん代入できる.
単純な代入が行えない,となると,中括弧でのインデクスリスト参照にはどんな使い道があるのか?
これがちゃんとあるのである.
matlab には,複数の文の評価結果を受け取る書式が存在する.
それは,小括弧 () と中括弧 {} と大括弧 [] の括弧 3 兄弟である.
ここでの小括弧 () は,関数呼び出しの引数指定に使う場合と,配列を参照する場合のものを指す.
例えば,
>> a={2,2};
>> b=[1,2;3,4];
>> b(2,2)
ans =
     4
>> b(a{:})
ans =
     4
となる.つまり,b(2,2) という指定の本質は,
「括弧内に 2 つの文があって,それぞれの評価結果が 2 になっている」
ということなのだ.だから,複数の文の評価をもたらす a{:} で置き換えることができた.
関数引数も同じ理屈だが,こちらはすぐにわかる実用性がある.varargin を利用して
function a = myfunc(varargin)
a = someotherfunc(varargin{:});
とすれば,即座にラッパーが書けるのだ.これは応用しやすい.
別に varargin でなくとも,一連の関数引数候補をセル配列にパックしておけば便利.多分.時々.
中括弧や大括弧は,複数の要素を連結するときに使う.
中括弧で連結すればセル配列になり,大括弧で連結すれば元の要素の属するクラスの配列になる.
このとき,要素を何で区切るか?といえば,カンマ , やセミコロン ; なわけで,これらも文の区切り記号であるところに注目である.
もっとも,連結するという文脈内では,カンマは 2 次元目連結,セミコロンは 1 次元目連結を示唆するものであって,
コンソールへの出力をする・しないという通常の文区切り時とはもちろん働きが違う.
セル配列の中括弧アクセスの場合,連結の括弧の中では全てカンマ区切りにみなされるらしい.
まとめると,セル配列への中括弧でのインデクスアクセスは,あくまで複数の参照文のシリアルな実行であるが故に,
元の配列の次元構成を保存できない.つまり,
>> a={1,2;3,4}
a =
    [1]    [2]
    [3]    [4]
>> {a{:,:}}
ans =
    [1]    [3]    [2]    [4]
となってしまうので気をつけよう.
多次元構成が保存されないことを除いてはなかなか便利に使える.例えば,数値のセル配列を数値配列にするには
b = [a{:}];
でいいわけだ.また,次元に関しては,代入先を予め設定しておいてから
b = zeros(size(a));
b(:) = [a{:}]'; %この転置はいるのか?手元の R2006a ではあってもなくてもエラーも出ないし結果も同じだった
のように代入するか,あるいは reshape を使って
b = reshape([a{:}],size(a));
などとすればよいだろう.
さて,セル配列のほかに不思議な振る舞いをする配列といえば構造体配列があるわけだが,こちらはまたの機会に.
こういった参照や連結の背景には subsref, subsasgn や subsindex といった参照用関数,そして cat, horzcat や vertcat といった連結用関数がある.
これらは matlab 上でオブジェクト指向プログラミングをしない限りあまり目にしないかもしれないが,背景として理解しておいてもいいと思う.

2013.06.10
本記事を大幅に拡充して「matlab: セル配列で困っている人へ」を執筆しました.


0 件のコメント:

コメントを投稿