Help us understand the problem. What is going on with this article?

MATLAB暗黙の型変換~その明暗

はじめに

MATLABの変数の間での「暗黙の型変換」についてのお話です.ちょっとした小技や落とし穴も.

※ 本記事の内容は MATLAB R2020b に基づいています.

変数の型?

変数の型をあまり意識しなくてもプログラムが書けるというのは,MATLABのありがたいことろですね.コードを書いている途中でも,変数が必要になったところで式の左辺に変数名を書くだけで即使うことができます.

Code
a = 1; % double

上のaの型は数値変数のデフォルトの型であるdoubleになります.

文字列の場合はちょっとだけ型を意識しますね.そう,''で囲んだ文字列は文字配列,""で囲んだ文字列はstring配列です.

Code
a_char = 'abc'; % 文字配列
a_str  = "def"; % string配列

このほかにも singleint8uint8charstringcellstructdatetimedurationtabletimetable・・・などなど色々な型があります(詳しくはドキュメンテーションをどうぞ).これらの型の間の変換のための関数も用意されていて,例えば double から single へ変換するには single() を使えばよいですし,数値を文字ベクトルに変換するには num2str()を使えばOK.

Code
x = 48;
x_single = single(x);
x_str = num2str(x);

暗黙の型変換の明暗

上で示したように明示的に型の変換を行うのに対して,MATLABが自動的に型の変換を行ってくれることがあります.それらの中で,便利に使えるケース(明),気を抜くと嵌まってしまうケース(暗)をいくつか紹介しましょう.

使って便利な型変換:文字配列とstring配列

暗黙の型変換で「使いで」があるのは文字列関連でしょうか.まずは基本的なところからスタートします.文字配列と文字配列の「加算」から.

例1:'0'+'1'=?

Code
a = '0';
b = '1';
c = a + b
Output
ans = 97

a,bともにchar型ですが,プラス演算子に渡されると,double(文字コードに相当する整数)に変換されてから加算され,doubleとして返されます.結果を再びchar型に戻すには,

Code
char(c)

とすればよく,結果は

Output
ans = 'a'

となります.'0'+'1'='a' なんて,なんか出来すぎ.

例2:'0'+'123'=?

Code
a = '0';
b = '123';
c = a + b;
char(c)
Output
ans = 'abc'

上のbは1行3列の文字配列なので,加算の際に同じ1行3列のdouble行列に変換されます.ですので,結果のcも1行3列で,charに変換すると'abc'になります.

例3:"0" + '123' = ?

Code
a = '0';
b = "123";
c = a + b
Output
c = "0123"

暗黙の型変換っぽくなってきました.プラス演算子の片側が文字配列,もう一方がstring配列の場合には,文字配列がstring配列に変換されてから連結されます.文字配列が二次元の場合にはこんな感じ.

Code
a = ['We    '; 'love  ';'MATLAB'];
b = ["!"];
c = a + b
Output
c = 3x1 string    
"We    !"    
"love  !"    
"MATLAB!"    

上の場合は,暗黙の型変換に加えて,「暗黙の配列の拡張」が行われているのですが(string配列["!"]を3行1列に拡張)その話はまたいずれどこかで.

小技1:+""

上で出てきた文字配列からstring配列への変換ルールを使うと,string() 関数を使う代わりに,+"" を文字配列の末尾にくっつけるだけでstring型への変換が出来ます.デバッグ中に,「あれっ?ひょっとしてここでstringに変換しなきゃならない?」と思ったところを軽くチェックするのに便利です.(デバッグ終わったらちゃんと string 関数を使いましょうね.)

Code
a = '我が輩は猫である'
Output
a = '我が輩は猫である'
Code
a = '我が輩は猫である'+""
Output
a = "我が輩は猫である"

小技2:

["要素A","要素B","要素C",...]の様に,「固定文字列+アルファベットの大文字」を並べたstring配列を作りたい時などに便利.ひらがな,カタカナ等にも応用できます.

Code
str = ("要素"+char('A'+(0:25)'))'
Output
str = 1x26 string    
"要素A"        "要素B"        "要素C"        "要素D"        "要素E"        "要素F"        "要素G"        "要素H"        "要素I"        "要素J"        "要素K"        "要素L"        "要素M"        "要素N"        "要素O"        "要素P"        "要素Q"        "要素R"        "要素S"        "要素T"        "要素U"        "要素V"        "要素W"        "要素X"        "要素Y"        "要素Z"        

何が起きているかは説明不要ですね.

知らずに嵌まる型変換:時刻・時間関連

時刻関連のデータ型,datetime配列, duration配列, calenderduration配列は,割と最近使われるようになってきたデータ型です.これらの型の変数に関する暗黙の型変換を無視していると足下をすくわれることがある(経験済)ので要注意です.

例1:タイムテーブルの時刻情報に加算

duration配列とdouble配列の演算の例として,timetableから時間軸情報を抜き出して,それに一定時間を加えた配列を取得することを考えます.

Code
tbl = timetable(seconds((0:29)'), sin( (0:29)/30*2*pi)');

% タイムテーブルの時間情報を取得
ts = tbl.Time
Output
ts = 30x1 duration    
0 秒          
1 秒          
2 秒          
3 秒          
4 秒          
5 秒          
6 秒          
7 秒          
8 秒          
9 秒          

Code
%  tsからdt秒後の時間ベクトルを生成
dt = 1;
ts2 = ts + dt
Output
ts2 = 30x1 duration    
86400 秒      
86401 秒      
86402 秒      
86403 秒      
86404 秒      
86405 秒      
86406 秒      
86407 秒      
86408 秒      
86409 秒      

タイムテーブルから取得した時間ベクトルに「1」を加えようとしただけなのに,「86400」も足されています.これは,duration型変数tsにdouble型変数dtを加えようとしていたため,double型からduration型への型変換が発生したことによる現象です.double型からduration型への変換の際に使われる単位は「days」であるため,dt の中身の数値1が「1日」に変換され,さらに「秒」単位に変換されてduration配列 ts に加算されます.正しくは次のように書くべきでした.

Code
dt = seconds(1);
ts2 = ts + dt
Output
ts2 = 30x1 duration    
1 秒          
2 秒          
3 秒          
4 秒          
5 秒          
6 秒          
7 秒          
8 秒          
9 秒          
10 秒         

例2:時刻・時間の比較

原理的には上の例と同じなのですが,より陥りやすい例としてduration配列をdoubleの数値と比較してしまうケースがあります.例えば,上の例と同じtimetable の20秒以降のデータを抜き出したい時に,次のようにやってしまうと上手くありません.

Code
threshold = 20;
tbl2 = tbl(tbl.Time > 20,:)
Output
tbl2 =

  0x1 の空の timetable

threshold が double なので,暗黙の型変換の結果,20日以降のデータを抜き出すことになってしまいます(データは30秒までしかないから結果的に空のテーブルが返されます).気をつけたいのは,この振る舞いがエラーでも何でも無くプログラムは正常に終了してしまう点です.数日以上のデータの中から最初の数秒だけ除外したつもりが,数日除外するなんてことにもなりかねませんね.

例3:duration 配列とdouble配列の連結

似たような例が,配列の連結もしくはテーブルの連結です.例えば,違う情報源から同じ形式のテーブルを持ってきて連結する場合に,片方の時間軸がduration型になっていてもう一方がdouble型だと値がおかしなことになってしまいます.この場合も,連結自体に問題は無く,エラー・警告などは全く出ないので,ランタイムエラーの温床になり得ます.

Code
t1 = (0:4)';
y1 = sin(t1/10*2*pi);
tb1 = table(t1, y1,'VariableNames',["Time","Value"]);

t2 = (5:9)';
y2 = sin(t2/10*2*pi);
tb2 = table(seconds(t2), y2,'VariableNames',["Time","Value"]);

tb3 = [tb1; tb2]
Time Value
1 0 秒 0
2 86400 秒 0.5878
3 1.728e+05 秒 0.9511
4 2.592e+05 秒 0.9511
5 3.456e+05 秒 0.5878
6 5 秒 0.0000
7 6 秒 -0.5878
8 7 秒 -0.9511
9 8 秒 -0.9511
10 9 秒 -0.5878

おわりに

暗黙の型変換は,知っていればコードがコンパクトになるなどの(可読性はともかく)利点があると同時に,知らないと嵌まる落とし穴があったりする,ニッチだけども味わい深いテーマだと思います.今回紹介した以外にも,便利な利用法や落とし穴があるかと思いますので,是非探索してみてください.

h583
MATLAB の中の人. 機械学習・深層学習修行中. All comments and opinions expressed are mine alone and do not necessarily reflect those of my employers, past or present.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away