構造体を分解し,各フィールドを個別の変数として取り出す.save/load や関数引数の処理などで便利なことがある |
- 利用する関数:fieldnames, eval, assignin
たとえば A という matlab の構造体変数があったとする.そしてそのフィールドが x, y, z だったとする.実際の中身は,
>> A.x = 1; A.y = rand(2); A.z = 'foo'
A =
x: 1
y: [2x2 double]
z: 'foo'
だったとしよう.このとき,構造体の各フィールドを,個別の変数としてワークスペースに展開(分解)したい場合がある.ベタに書けば,
>> x = A.x;
>> y = A.y;
>> z = A.z;
としたいわけだ.実用例(?)として load がある.A = load('filename'); とすると,save 時の各変数名は A のフィールド名となって,データは一つの構造体変数 A にまとめられる.これを分解したい場合があろうということだ.
この分解操作を自動的に簡単に行う方法を作っていこう.使用イメージが
>> field_decompose(A);
となるような関数 field_decompose を作っていくわけだ.なお,構造体自体が配列(=構造体配列)になっている場合は,今回は対象としない.
第一段階として,わかりやすさのために,スクリプト field_decompose_s を作ってみることにする.スクリプトなので,簡単のため,分解対象とする構造体名は A に固定する.スクリプトだと,スクリプト内でうかつに変数を使うとすでに使っていた変数が上書きされたり,スクリプト内部用の変数が残ったままになったりするが,この段階では気にしない.あとで関数版を作るので,この問題は解消される.
まず,構造体(ここではA)のフィールド名(ここではx,y,z)の一覧を取得しておく.関数 fieldnames を使う.
>> F = fieldnames(A);
fieldsnames は文字列のセル配列を返す.この例では
>> whos F
Name Size Bytes Class Attributes
F 3x1 342 cell
>> F
F =
'x'
'y'
'z'
となる.F{1} の中身は文字列 'x',F{2} の中身は文字列 'y' となっている.
次に,実際に個別の変数に分解する.上の x = A.x と同様のことをやりたいのだが,ここで問題がある.変数のフィールド名は,自動化プログラムの作成時点で予め分かるわけではないのだ.今は A.x, A.y, A.z とした例を見ているが,このプログラム(field_decompose)自体は,どんなフィールドを持つかわからない前提で動作しなければならない.それには,代入式の左辺も変数で指定できるようなんらかのトリックが必要になる.そうしたトリックは matlab にはいくつか用意されている.いまの段階ではスクリプトを作っているので,ワークスペースは呼び出し側とつながっている.そこで,単純に関数 eval を使うことができる.
eval(s) は,文字列 s の中身を,matlab の式(文といってもよい)として実行する.eval('3*2')
と
3*2
は,同じ結果をもたらす.さて,いまやりたいのは,「もしフィールで名が 'x' なら 'x=A.x;' を実行したい」であった.すると,
x = A.x;は,eval によって
eval('x = A.x;')
とも書ける.さらにこれは,s = 'x=A.x;' のとき,
eval(s)
とも書ける.もう一歩,これは s = sprintf('%s=A.%s', F{1}, F{1}); と書けるから,
eval(sprintf('%s=A.%s', F{1}, F{1}))
と書いてもよいわけだ.ここまでくればもうできそうな気がしませんか?ちなみに,sprintf を使わず
eval([F{1}, '=A.', F{1}, ';'])
と書いてもよい.
ここまで試してみよう.
clear all
A.x = 1; A.y = rand(2); A.z = 'foo';
F = fieldnames(A);
eval([F{1}, '=A.', F{1}, ';'])
これを実行して whos で確認すると
>> whos
Name Size Bytes Class Attributes
A 1x1 574 struct
F 3x1 342 cell
x 1x1 8 double
と, 見事に変数 x がワークスペース内に作成されている!
>> x
x =
1
と,中身も A.x と一致している.
あとはこれをループにして,全フィールドに対して行えばよい.
clear all
A.x = 1; A.y = rand(2); A.z = 'foo';
F = fieldnames(A);
for n=1:length(F)
eval([F{n}, '=A.', F{n}, ';'])
end
確認:
>> whos
Name Size Bytes Class Attributes
A 1x1 574 struct
F 3x1 342 cell
n 1x1 8 double
x 1x1 8 double
y 2x2 32 double
z 1x3 6 char
>> x,y,z
x =
1
y =
0.9572 0.8003
0.4854 0.1419
z =
foo
ばっちりである.手作業でやる場合には,
F = fieldnames(A);
for n=1:length(F)
eval([F{n}, '=A.', F{n}, ';'])
end
の A を所望の構造体名にしてコピペすれば事足りる.
さて,方法の基礎がわかったところで,
>> field_decompose(A);
とできる関数 field_decompose に仕立て上げることを考えよう.
今度は,上記のように eval を使っても,関数内で変数が生成され,関数から戻るときに破棄されて,結局特に何も起こらなくなってしまう.関数内でなく,関数を呼びだした側のワークスペースに変数を生成するには,また別のトリックがいる.ここで有効なのが assignin という関数だ.
assignin('caller', 'x', A.x)
とすると,関数呼び出し側のワークスペース (caller workspace) 中の変数 x に,A.x の中身を代入してくれる.変数名(フィールド名)は実行時にならないとわからないわけだが,地の文でなく assignin の引数なのだから,次のように書ける.
assignin('caller', F{1}, A.(F{1}))
A という構造体変数名も可変だと思われるかもしれないが,関数の入力引数を A という名前受けていれば,もとの名前と関係なく関数内では A で参照することになる.なお,A.(文字列) というフィールド名の動的参照を利用している.
ループ化と合わせて一気に関数を書いてしまおう.field_decompose.m というファイルを作って,その中身を
function field_decompose(A)
F = fieldnames(A);
for n=1:length(F)
assignin('caller', F{n}, A.(F{n}));
end
とする.できたら早速試してみよう.
clear all
B.x = 1; B.y = rand(2); B.z = 'foo';
field_decompose(B);
実行できただろうか.エラーなく実行できたら,結果を確認しよう.
>> whos
Name Size Bytes Class Attributes
B 1x1 574 struct
x 1x1 8 double
y 2x2 32 double
z 1x3 6 char
>> x,y,z
x =
1
y =
0.6557 0.8491
0.0357 0.9340
z =
foo
ばっちりだ.めでたしめでたし.
~追記~
今回使った技は,
eval, assignin, 構造体のフィールド名の動的参照
といったあたりだ.興味のある読者は,matlab のヘルプなどを参照してより深く使いこなして頂きたい.
当ブログの関連記事を以下に挙げておく.ご参考まで.
- matlab: 壁の外へ 1. 前置き : ワークスペースという概念の解説
- matlab: 構造体配列の参照法とその周辺 : 構造体の,配列としての参照法
- matlab: セル配列で困っている人へ : セル配列のわりと詳細な解説.当ブログの人気記事.
- matlab: 構造体の利用例: ワークスペース上の変数を一つの構造体にまとめる: 本記事と逆の操作.セットで習得しておくと便利.
0 件のコメント:
コメントを投稿