2015年4月28日火曜日

matlab: 構造体の利用例:構造体の各フィールドをワークスペース上の個別の変数に分解する

matlab の構造体と言えば,いくつかの変数を一つの塊にまとめてパッケージしたようなものだ.以前,ワークスペース上の変数を一つの構造体にまとめるという記事を書いた.今回は,逆に,一つの構造体から,個々の項目を別個の変数として,ワークスペース上に分解する方法を述べる.
構造体を分解し,各フィールドを個別の変数として取り出す.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 のヘルプなどを参照してより深く使いこなして頂きたい.

当ブログの関連記事を以下に挙げておく.ご参考まで.

0 件のコメント:

コメントを投稿