9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MATLAB/SimulinkAdvent Calendar 2023

Day 19

MATLABでcell配列は何?なんか美味しそうですけど、普通の配列とどう違う?

Last updated at Posted at 2024-04-26

はじめに

セリセロリって名前は似ていますが、どういう関係か?つい疑問に思ってしまったのですが、ここはそれについて説明する記事ではないです。

MATLABを使っていたら「cell配列」という概念にぶつかるのですね。最初は「何それ美味しいの?」と思ってしまいました。

私は仙台でセリ鍋を食べたことがあるからこれは美味しそうに聞こえますよね。

全然関係ない話ですけど……。

基本的にMATLABで出番が多いのはただの「配列」(array)で、その配列の仕組みについて前回の記事で詳しく紹介しました。

しかしその他にも「cell配列」というものもよく使われています。ただの配列と似ていますが、違うところもあります。

それにこれは他の言語にはない概念でもあるので、他の言語の経験と知識から説明することはちょっと難しいです。Pythonのlist、tuple、setとも違います。

セロリが嫌で食べられない人もいるとよく聞いた話ですが、cell配列も最初はわけわからなくて嫌だという人もいるでしょうね。

でもちゃんと扱い方と必要性を理解したらこれもプログラミング言語として興味深いと感じます。

ということで、今回の記事では「cell配列」について解説します。

cell配列の基本

cell配列とは

簡単に言うとcell配列はMATLABにおける配列の一種です。

普通の配列と同じように色んなものが入っている配列ですが、普通の配列が同じデータ型のものから成されるのに対し、cell配列は色々違うものを同時に入れることができます。

これはPythonのlistやRuby、JavaScript、Dart、PHPなどの配列と似ていますね。ただし使い方はもっと複雑で、そう簡単にはいかないのです。

どう違うか以下説明していきます。

cell配列の作成

普通の配列は[ ]で囲むことで作るのに対し、cell配列は{ }で作るのです。

celery = {3 "4" '5'}

又は

celery = {3,"4",'5'}
結果
celery =

  1×3 の cell 配列

    {[3]}    {["4"]}    {'5'}

こうやって違うものを入れてもそのまま問題ないのはcell配列の一番わかりやすい特徴です。

試しにこのような違うものを混ぜて普通の配列を作ってみたら勝手に同じものに変換させられますね。(変換できない場合はエラー)

ar = [3 "4" '5']
結果
ar = 

  1×3 の string 配列

    "3"    "4"    "5"

配列の中にcell配列を入れることもできます。これは「ネスト」と呼びます。普通の配列はネストできません。

{0,1,{2,3}}
結果
    {[0]}    {[1]}    {1×2 cell}

ネストできるだけでなく、普通の配列と同様に2次元又はまたともっと多い次元にできます。

{1 "2" {3};'4' [] [5 6]}
結果
  2×3 の cell 配列

    {[1]}    {["2"     ]}    {1×1 cell}
    {'4'}    {0×0 double}    {[   5 6]}

ただし、普通の配列みたいに数学の演算子など使うことができません。

{1}+{2}
結果
'cell' タイプのオペランドに対して、演算子 '+' はサポートされていません。

PythonのりlistやRubyの配列などは+で連結することができますが、MATLABのcell配列はそんなことはできませんね。

cell配列の要素を取る

cell配列も配列だから、後ろに数字が入った括弧( )を書くことで要素にアクセスすることができます。ただし出てくるのはcell配列のままです。

celery = {5 6 7}
celery(1)
結果
  1×1 の cell 配列

    {[5]}

1×1サイズになったとしてもまだcellという状態なので、そのまま使うことはできません。

cellは何か中身を包む宝箱で、解除しないと中身を取り出して扱うことができないですね。

cellの中の要素を取りたいなら( )の代わりに、{ }を使います。

celery{1}
結果
     5

数値の配列を入れて同時に複数の要素を取ることもできますが、それがバラバラに出てきてしまいます。

celery = {11 22 33 44 55};
celery{[1 3 4]}
結果
ans =

    11


ans =

    33


ans =

    44

そこで複数の配列を受け皿にすることができます。

celery = {111 222 333 444 555 666};
[s,e,r,i] = celery{2:5}
結果
s =

   222


e =

   333


r =

   444


i =

   555

又は[ ]で囲んで配列に纏めます。

ar = [celery{[1 3 4]}]
結果
ar =

   111   333   444

書き方はなんか複雑になってしまいますね。だからちゃんと理解しておかないとわけわからなくて悶々としてしまって困りますよね。

配列の中の全部のcellを解除する

{:}をcell配列の後ろに付けると中身が全部はみ出ます。ただしそのままだとバラバラに出てしまうので、配列に纏めたいなら[ ]で囲んでおきましょう。

celery = {12 34 56};
[celery{:}]
結果
    12    34    56

また、もし要素が配列だったらそこで連結されます。

celery = {[1;2] [3;4] [5;6]};
[celery{:}]
結果
     1     3     5
     2     4     6

ただし形が合わないなどの原因で連結できない場合はエラーになるので注意です。

celery = {12 [3;4] 56};
[celery{:}]
結果
次を使用中のエラー: horzcat
連結する配列の次元が一致しません。

このエラーメッセージを見ると、実際にここでhorzcat関数が呼ばれるという仕組みになっていますね。この動作を理解していなければこれはわけわからないエラーメッセージだと思って挫折してしまうかもしれません。

解除されたcell配列の中身を関数の入力に使う

cellからはみ出るものは変数に収める他に、直接そのまま関数に入れて使うこともできます。

例えば3つ数値を入れる必要がある関数を作って、それをcell配列に入れておいて、使う時に{:}ではみ出して関数に入力することができます。

function soukei(a,b,c)
    fprintf("%d+%d+%d = %d\n",a,b,c,a+b+c)
end

celery = {1 2 5};
soukei(celery{:});
結果
1+2+5 = 8

cell配列の要素を書き換える

普通の配列と同様に、cell配列の要素にアクセスできたら書き換えることができます。

celery = {1 2 3};
celery{2} = [4 5];
celery
結果
celery =

  1×3 の cell 配列

    {[1]}    {[4 5]}    {[3]}

ただし( )でアクセスしたらエラーになるので、{ }を使わないとならないですね。

celery = {1 2 3};
celery(2) = 4;
結果
double から cell に変換できません。

このエラーメッセージから見ると、代入しようとする時に勝手に変換しようとされますね。
ではもしその代わりにcellとして入れたら上手くいくのではないかと思ったら、本当にできてしまいます。

celery = {4 5 6};
celery(2) = {[7 8]};
celery
結果
celery =

  1×3 の cell 配列

    {[4]}    {[7 8]}    {[6]}

cell配列に関わる関数

cell

cellという関数を使ったら空っぽなcell配列を作成することもできます。

cell(4,2)
結果
  4×2 の cell 配列

    {0×0 double}    {0×0 double}
    {0×0 double}    {0×0 double}
    {0×0 double}    {0×0 double}
    {0×0 double}    {0×0 double}

ただし数値一つ入れたらそのサイズのとなります

cell(3)
結果
  3×3 の cell 配列

    {0×0 double}    {0×0 double}    {0×0 double}
    {0×0 double}    {0×0 double}    {0×0 double}
    {0×0 double}    {0×0 double}    {0×0 double}

cell2mat

cell配列を解除して普通の配列にする時に{ }を後ろに付ける他にcell2matを使うこともできます。これを使うと多次元のcell列をその形のまま普通の配列に変換できます。

celery = {3 4 5;7 8 9};
cell2mat(celery)
結果
     3     4     5
     7     8     9

中の要素が配列だったら勝手に連結してしまいます。

celery = {[1;4] [2 3;5 6];7 [8 9]}
mat = cell2mat(celery)
結果
celery =

  2×2 の cell 配列

    {2×1 double}    {2×2 double}
    {[       7]}    {[     8 9]}


mat =

     1     2     3
     4     5     6
     7     8     9

ただし連結できないような形だったらエラーになります。

celery = {[5;6],[7 8]}
mat = cell2mat(celery)
結果
celery =

  1×2 の cell 配列

    {2×1 double}    {[7 8]}

次を使用中のエラー: cat
連結する配列の次元が一致しません。

エラーメッセージから見ると、中でcat関数が使われるという仕組みだとわかります。

mat2cell

mat2cell関数はcell2matとは逆に、普通の配列からcell配列を作る関数です。

ただし使う時にそれぞれの軸でどのように配列を分割するか指定する必要があります。

mat = ones(5,4);
mat2cell(mat,[2 2 1],[3 1])
結果
  3×2 の cell 配列

    {2×3 double}    {2×1 double}
    {2×3 double}    {2×1 double}
    {[   1 1 1]}    {[       1]}

1次元の横方向の配列の分割にもこのように使えます。

mat = [2 2 1 3 3 3];
mat2cell(mat,1,[2 1 3])
結果
  1×3 の cell 配列

    {[2 2]}    {[1]}    {[3 3 3]}

num2cell

mat2cellと違って、分割せずに単に配列をそのままcell配列に書き換えるならnum2cellを使います。

mat = reshape(1:12,3,4)
celery = num2cell(mat)
結果
mat =

     1     4     7    10
     2     5     8    11
     3     6     9    12


celery =

  3×4 の cell 配列

    {[1]}    {[4]}    {[7]}    {[10]}
    {[2]}    {[5]}    {[8]}    {[11]}
    {[3]}    {[6]}    {[9]}    {[12]}

軸を指定たらその軸だけ纏めて一緒のcellに入れることになります。

mat = ones(4,3,2);
celery1 = num2cell(mat,1)
celery2 = num2cell(mat,2)
celery3 = num2cell(mat,3)
celery13 = num2cell(mat,[1 3])
結果
  1×3×2 の cell 配列

celery1(:,:,1) = 

    {4×1 double}    {4×1 double}    {4×1 double}


celery1(:,:,2) = 

    {4×1 double}    {4×1 double}    {4×1 double}


  4×1×2 の cell 配列

celery2(:,:,1) = 

    {[1 1 1]}
    {[1 1 1]}
    {[1 1 1]}
    {[1 1 1]}


celery2(:,:,2) = 

    {[1 1 1]}
    {[1 1 1]}
    {[1 1 1]}
    {[1 1 1]}


celery3 =

  4×3 の cell 配列

    {1×1×2 double}    {1×1×2 double}    {1×1×2 double}
    {1×1×2 double}    {1×1×2 double}    {1×1×2 double}
    {1×1×2 double}    {1×1×2 double}    {1×1×2 double}
    {1×1×2 double}    {1×1×2 double}    {1×1×2 double}


celery13 =

  1×3 の cell 配列

    {4×1×2 double}    {4×1×2 double}    {4×1×2 double}

celldisp

普段cell配列の中身が複数行の配列などだったらその配列の内容が表示されませんね。

celery = {0,1;ones(2),zeros(3)}
結果
  2×2 の cell 配列

    {[       0]}    {[       1]}
    {2×2 double}    {3×3 double}

そこでcelldisp関数を使ったらcell配列の中身を詳しく表示することができます。

celldisp(celery)
結果
celery{1,1} =
 
     0

 
 
celery{2,1} =
 
     1     1
     1     1

 
 
celery{1,2} =
 
     1

 
 
celery{2,2} =
 
     0     0     0
     0     0     0
     0     0     0

cell配列が出る関数

直接作らなくても実はcell配列は色んな場面で出てきます。

例えばexcelやcsvファイルなどを読み込むために使うreadcell関数です。

celery.csv
1,セル,cell
2,セリ,seri
3,セロリ,celery
readcell("celery.csv")
結果
  3×3 の cell 配列

    {[1]}    {'セル' }    {'cell'  }
    {[2]}    {'セリ' }    {'seri'  }
    {[3]}    {'セロリ'}    {'celery'}

又はjsonを読み込むjsondecode関数

celery.json
[
    ["セロリ","celery"],
    [1,2],
    {"セリ":"seri"}
]
jsondecode(fileread("celery.json"))
結果
  3×1 の cell 配列

    {2×1 cell  }
    {2×1 double}
    {1×1 struct}

それと、機械学習などで沢山な画像を読み込むのに使うimageDatastoreも、readallメソッドを使ったらcell配列になります。これについて詳しくはこの記事で紹介しています。よろしければこれも読んでください。

このように色んな場面でcell配列が使われるので、どうやって対処するかわかっておくのも大事でしょう。

参考&もっと読む

終わりに

ここまで読んでセロリを食べたくなってきたでしょうか?大体cell配列についてわかってきたでしょうか。

他の言語と比べてちょっと風変わりな概念だからちょっとわかりにくいかもしれませんが、これを理解したら面白く感じます。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?