Octaveでfileを利用する話

ここでは非常に安易にファイルを利用する方法について述べる。 従って、ここで説明する命令に関しても、その機能の一部のみを用い、 全てを活用している訳では無い。

安易なデータファイルの処理

数値行列のみを含むファイルの場合は単純にload命令で対処可能である。 以下にload命令を用いてnekoとneko.matrixと云った名前のファイルの 読み込み例を示す。尚、ここでファイルの中身は以下に示す内容である;

1 2 3
4 5 6
	    

octave:1> load neko
octave:2> neko
neko =

  1  2  3
  4  5  6

octave:3> load neko
warning: load: local variable name `neko' exists.
warning: use `load -force' to overwrite
error: load: unable to load variable `neko'
error: evaluating index expression near line 3, column 1
octave:3> load -force neko
octave:4> neko
neko =

  1  2  3
  4  5  6

octave:5>load neko.matrix
warning: load: local variable name `neko' exists.
warning: use `load -force' to overwrite
error: load: unable to load variable `neko'
error: evaluating index expression near line 5, column 1
octave:5> load -force neko.matrix
octave:6> who

*** local user variables:

neko

octave:7> 
 
	    

このload命令を用いた場合、データは自動的にファイル名から拡張子を 除いた名前が設定される。更にload命令を用いた場合、同じ名前の変数 が存在される場合には旧来のデータは保護される。これは二度目に同じ ファイルをload命令で読み込もうとした際にエラーが生じている理由で ある。又、ファイル名が別でも拡張子を除くと同じ名前になる場合、 即ち、ここでの例で、nekoとneko.matrixの場合にも同じ理由でエラー になる。これらの場合には load命令に-forceオプションを設定すれば 良い。

しかし、通常のデータファイルではコメントを含んでいたり、或いは、 数値行列以外の形式の場合もあり、この様な場合にはload命令のみで 対処する事は困難である。

又、行列データの保存にはsave命令が使える。このsave命令では whoを実行して表示されるデータ全てのファイルへの保存と、 個別のデータの保存の両方が行え、データの追加も行える。

octave:1> a=rand(4,4);
octave:2> b=rand(3,1);
octave:3> save neko a
octave:4> save test
octave:5> who

*** local user variables:

a  b


	    

この例では、a,bをrandで生成した4x4と3x1行列とする。save neko a で、行列aをファイルnekoに保存し、save testでwhoで表示される データ全てをファイルtestに保存する。実際、ファイルnekoと ファイルtestは次の通りである;

ファイルnekoの内容
# Created by Octave 2.0.16, Thu May 10 08:21:44 2001 
# name: a
# type: matrix
# rows: 4
# columns: 4
 0.590789258480072 0.222358718514442 0.876821994781494 0.949454307556152
 0.741063475608826 0.656238257884979 0.365377485752106 0.979949235916138
 0.395543217658997 0.417380422353745 0.444111585617065 0.857901215553284
 0.568090081214905 0.558982253074646 0.0379265695810318 0.475694209337234

	    
ファイルtestの内容
# Created by Octave 2.0.16, Thu May 10 08:21:53 2001 
# name: a
# type: matrix
# rows: 4
# columns: 4
 0.590789258480072 0.222358718514442 0.876821994781494 0.949454307556152
 0.741063475608826 0.656238257884979 0.365377485752106 0.979949235916138
 0.395543217658997 0.417380422353745 0.444111585617065 0.857901215553284
 0.568090081214905 0.558982253074646 0.0379265695810318 0.475694209337234
# name: b
# type: matrix
# rows: 3
# columns: 1
 0.628571033477783
 0.415022879838943
 0.216913774609566
	    

又、ファイルへのデータの追加は-appendオプションを付けると良い。

octave:6> c=[1,2,3];
octave:7> save -append test c
	    
ファイルtestの内容(cを追加後)
# Created by Octave 2.0.16, Thu May 10 08:21:53 2001 
# name: a
# type: matrix
# rows: 4
# columns: 4
 0.590789258480072 0.222358718514442 0.876821994781494 0.949454307556152
 0.741063475608826 0.656238257884979 0.365377485752106 0.979949235916138
 0.395543217658997 0.417380422353745 0.444111585617065 0.857901215553284
 0.568090081214905 0.558982253074646 0.0379265695810318 0.475694209337234
# name: b
# type: matrix
# rows: 3
# columns: 1
 0.628571033477783
 0.415022879838943
 0.216913774609566
# name: c
# type: matrix
# rows: 1
# columns: 3
 1 2 3
	    

尚、ファイルの保存の場合はloadと異なり、指定したファイルが存在して いても、無警告で処理が実行される。実際、testファイルが存在する状態 でtestに行列aとcを保存する例を以下に示す。

octave:8> save test
octave:9> save test a c
octave:10> 
	    
ファイルtestにa,cを書き込んだ場合
# Created by Octave 2.0.16, Thu May 10 08:29:54 2001 
# name: a
# type: matrix
# rows: 4
# columns: 4
 0.590789258480072 0.222358718514442 0.876821994781494 0.949454307556152
 0.741063475608826 0.656238257884979 0.365377485752106 0.979949235916138
 0.395543217658997 0.417380422353745 0.444111585617065 0.857901215553284
 0.568090081214905 0.558982253074646 0.0379265695810318 0.475694209337234
# name: c
# type: matrix
# rows: 1
# columns: 3
 1 2 3
	    

又、saveで保存したデータはload命令でそのまま読み込める;

octave:1> load test
octave:2> who

*** local user variables:

a  c

octave:3> a
a =

  0.590789  0.222359  0.876822  0.949454
  0.741063  0.656238  0.365377  0.979949
  0.395543  0.417380  0.444112  0.857901
  0.568090  0.558982  0.037927  0.475694

octave:4> c
c =

  1  2  3

octave:5> 
	    

ここで、MATLABやOctaveではC言語風にファイルの操作が可能である。 この機能を用いれば、非常に複雑な形式のファイルの読み込みや書込み が可能である。但し、Octaveの方が処理言語の機能が上である為、MATLAB でも利用可能なプログラムを構築する必要がある場合は注意が必要である。 その為、ここではMATLABとの互換性に留意して以下の記述を纏めており、 独自の機能は利用しない様にしている。

ファイルのOpenとClose

ファイルのOpenはfopen命令を用いる。これはid=fopen(filename,mode) となっており、filenameは'neko'や'test/neko.dat'の様なディレクトリ を含めたファイル名前を指定する。尚、modeは以下に示すとおり;

  1. 'r' :読み込み
  2. 'w' :新規に書込み
  3. 'a' :末尾に追加
  4. 'r+' :読み込み。内容の更新も行える。
  5. 'w+' :書き込み。内容の更新も行える。
  6. 'a+' :末尾に追加。内容の更新も行える。

又、fopenから返される値idは以降のファイル操作で利用し、操作する ファイルの指定に利用される。尚、ファイルが存在しない等のファイルの オープンでエラーを出した場合には-1が返されるので、この返却値を利用 してエラー処理を行う事も可能である。次に、ファイルのcloseはC言語と 同様にfclose(id)で行う。更に、ファイルの先頭にポインタを動かす場合、 frewindを用いる。又、データの読み込みや書き込みではfgets,fputs, fscanf等が存在し、基本的な処理はC言語と同様の命令が揃っている 但し、実際の機能はやや異なるので注意が必要である。特にMATLABとの 互換性を考慮すると、Octaveの機能をフルに活用した場合に書き換えが 必要になる。これは入力と出力書式の指定で顕著であり、MATLABでは この指定が細かく行えなず、基本的に行単位で書式が固定される。 その為、一行に整数、文字列、浮動小数が混在する場合には、入出力 の書式を指定して読み込む方式は避け、fgetsで一行をstreamとして 取り込み、streamを分解してsscanfで形式の変換を行う事を薦める。

データの読み込み

ファイルのデータ読み込みはfgets,fscanf等が利用可能である。 尚、Octaveのfscanf命令は上述の様にMATLABのものと比べ機能が強化され ており、その上、C-flagを立てる事によってC言語のfscanfと同じ利用が 可能となっている。但し、MATLABでは書式が行毎で数値や文字単位では ない為、Octave専用のプログラムになってしまうので、MATLABとの互換性 を重視したプログラムでは、文字、数値が混在する行を扱う場合にはfgets を用いて一行をstreamとして取り込み、そのstreamをパターンマッチング や長さで分割して、それらに対してsscanfを用いて形式の変換を行う方が 処理は繁雑になるかもしれないが安全である。

以下に単純な実例を示す。ここでの例では単純な数値行列データに 日本語を含めた文字列を含むものである。このファイルの名前を neko.txtとし、その内容を以下に示す;

1 2 3
3 2 1
はい、1 2 3ある日森の中、熊さんに出逢った。
	    

このファイルは1行と2行が数値で、3行目は数値と文字の混在である。 このファイル('neko.txt')をOctaveで開き、fgetsを用いて一行づつ読み 込む様子を以下に示す。

octave:43> fid=fopen('neko.txt','r');
octave:44> L1=fgets(fid)
L1 = 1 2 3

octave:45> L2=fgets(fid)
L2 = 3 2 1

octave:46> L3=fgets(fid)
L3 = はい、1 2 3ある日森の中、熊さんに出逢った。

octave:47> frewind(fid)
ans = 0
octave:48> L4=fgets(fid)
L4 = 1 2 3
	    

この例で示す様に、ファイルを開いて内容を参照する場合には'r' か'r+'オプションで開く。間違って'w'や'w+'とするとファイルは 更新されて、以前の内容が消去された状態となるので注意する。 又、fgetsは例で示す様にファイルの先頭から一行づつ読み込む。 その為、ファイルの先頭に行く場合はfrewind(fid)を利用する。

ところで、fgetsで返される値は実はストリングである。例えば、 一見するとベクトルにしか見えないL1はストリングである。 実際、L1の和を計算しようとすると以下のエラーが出る;

octave:56> L1+L1
error: invalid conversion from string to real matrix
error: invalid conversion from string to real matrix
error: evaluating assignment expression near line 56, column 3
octave:56> size(L1)
ans =

  1  6

octave:57> L1
L1 = 1 2 3
	    

この例の様に、テキストファイルデータはfgetsによって文字列として 読み込まれる為、必要に応じて変換して行く必要がある。この変換は C言語と同様にsscanfを用いる。ここでは一行目のストリームL1の変換を 行う。尚、sscanfのmodeのd,s,fは各々整数、文字列、浮動小数へと変換 する事を意味する。特にdの場合、Octaveでは自動的に列ベクトルになる 事に注意する。これはOctaveが列ベクトルを標準とする為で、MATLABの 場合は行ベクトルになる。この点はOctaveとMATLABの両方で動作する プログラムを作成する必要がある場合、特に注意が必要な個所である。

octave:51> a11=sscanf(L1,'%d')
a11 =

  1
  2
  3

octave:52> a12=sscanf(L1,'%s')
a12 = 123
octave:53> a12=sscanf(L1,'%f')
a12 =

  1
  2
  3

octave:54> a11=sscanf(L1,'%d')
a11 =

  1
  2
  3

octave:55> a12=sscanf(L2,'%d')
a12 =

  3
  2
  1

octave:56> a11+a12
ans =

  4
  4
  4
	    

この様にファイルが文字列か数字列の何れかで構成されている場合、 fgetsで行毎読み、sscanfで形式の変換を一度に行えば良い。然し、 問題はL3の様に数と文字が混合している場合である。L3の様に意地の 悪い代物は、悪意を持って例として示しているので仕方が無いとしても、 次のファイル(tama)の様な形式のものの場合は非常にありふれている だろう。

# No. Flag  Value
1     t      10
2     f     -10
3     t      20
4     f     -20
	    

この様なデータの場合、sscanfで一気に変換する事は意味が無く、 本来ならC言語の様に書式を指定して各々を変換するのが本来の姿で あろう。然し、MATLABには与えられたストリームを一気に変換する 事しか出来ない。但し、Octaveの場合にはCフラグを立てる事で何とか 対処可能である。ところが、MATLABにはこのフラグを持たない為に、 この機能を利用するとMATLABとの互換性に問題が生じる。

ここでは、ファイルtamaを開き、安易に一行づつsscanfで整数('%d')や 文字列('%s')へと一気に変換した例を示す。

    
octave:73> fid2=fopen('tama','r')
fid2 = 3
octave:74> tama1=fgets(fid)
tama1 = # No. Flag  Value

octave:75> tama2=fgets(fid)
tama2 = 1     t      10

octave:76> tama3=fgets(fid)
tama3 = 2     f     -10

octave:77> tama4=fgets(fid)
tama4 = 3     t      20

octave:78> tama5=fgets(fid)
tama5 = 4     f     -20

octave:79> st1=sscanf(tama1,'%d')
st1 = [](0x1)
octave:80> st1=sscanf(tama2,'%d')
st1 = 1
octave:81> st1=sscanf(tama3,'%d')
st1 = 2
octave:82> st1=sscanf(tama4,'%d')
st1 = 3
octave:83> st1=sscanf(tama5,'%d')
st1 = 4
octave:84> st1=sscanf(tama1,'%s')
st1 = #No.FlagValue
octave:85> st1=sscanf(tama2,'%s')
st1 = 1t10
octave:86> st1=sscanf(tama3,'%s')
st1 = 2f-10
octave:87> st1=sscanf(tama4,'%s')
st1 = 3t20
octave:88> st1=sscanf(tama5,'%s')
st1 = 4f-20
	    

この例で示す様にsscanfによる変換では形式に対応しない個所以降の 列は除外されてしまう。例えば、'%d'で整数に変換する場合には、 二列目のflagに当たって変換が途中で終り、flagの前のNo.に相当する 数のみが返されている。又、ファイル先頭行の文字列は変換が出来ずに 空行が返されている。更に、ストリームを一気に文字列に変換した場合、 spaceやtabは省略されている事(2 f -10が2f-10に変換される等)に注意 する。

この様にMATLABのsscanf,fscanfではこれ以上の処理が出来ない(但し、 上の例ではOctaveによる)が、Octaveの場合にはCフラグを立てると C言語と同様に複雑な形式のファイルにも対応可能である。

octave:94> [s1,s2,s3,s4]=sscanf(tama1,'%s %s %s %s','C')
s1 = #

s2 = No.

s3 = Flag

s4 = Value

octave:95> [n1,flg,n3]=sscanf(tama2,'%d %s %d','C')
n1 = 1

flg = t

n3 = 10

	    

この様にOctaveであれば、よりC言語風にsscanfやfscanfを利用する事が 可能となるが、このCフラグを立ててしまうと今度はMATLABでは利用出来 ない。この場合、スマートでは無いが、次の様にストリームを分けて対処 すれば互換性に問題が生じる事は無い。

octave:96> find(tama2=='t' | tama2=='f')
ans = 7
octave:97> n1=sscanf(tama2(1:6),'%d') 
n1 = 1
octave:98> n2=sscanf(tama2(8:length(tama2)),'%d')
n2 = 10
octave:99> flg=sscanf(tama2(7),'%s')
flg = t
	    

ここで、最初にfindを用いている個所はストリームtama2からフラグ値の t又はfのどちらかのある個所を求め、その個所から前後に分けて整数に 変換している。尚、'|'はOctave/MATLABの論理和である。

同様にコメント行かどうかは'#'がストリームに存在するかどうか検証 するだけで良い。この場合、全てを一旦文字列に変換し、その先頭が '#'であるかを判別する様にすれば良い。

ここで重要な事に、OctaveとMATLABの両方では、扱う行列データは文字 か数値のみしか許容されず、両者が混在した行列を扱う事は出来ない事 に注意が必要である。尚、この性質がsscanfやfscanfの制約となって 表われていると思われる。

この様に行列データに制約がある為、文字と数値が混在する表の扱いで、 色々な行列データを準備する必要がある様に思えるが、OctaveとMATLAB の双方にC言語の構造体と同様のデータ構造を用いる事が可能の為、 そちらを使うと多くの行列を利用する必要が無くなる。更に、MATLABと Octaveで 構造体を利用する場合は、他の変数と同様に予め宣言する必要 は無く、以下の例の様に直接利用しても構わない。

octave:102> [neko.n1(1),neko.flg(1),neko.n2(1)]=sscanf(tama2,'%d %s %d','C')
neko.n1 = 1

neko.flg = t

neko.n2 = 10

octave:103> [neko.n1(2),neko.flg(2),neko.n2(2)]=sscanf(tama3,'%d %s %d','C')
neko.n1 = 2

neko.flg = f

neko.n2 = -10
	    

この例ではsscanfで変換したデータをneko.n1,neko.flg,neko.n2に 収納している。ここで各々の配列は数値と文字列である事に注意する。 MATLABとOctaveではこの様に構造体を用いて文字と数値が混在した データを一括して扱う。尚、データが構造体かどうかは直接データ 名を入力する事でも判るが、この場合は全てのデータが表示される 為、Octaveの場合はis_structで構造体かどうかを判定し、struct_elements で構造体の構造を調べられる。但し、MATLABの場合は命令の名前は 似ているが、全く別の命令を用いるので互換性が無い為に注意が必要で ある。

octave:104> neko
neko =
{
  n2 =

     10
    -10

  flg =

t
f

  n1 =

    1
    2
}

octave:107> is_struct(neko)
ans = 1
octave:108> 

octave:109> struct_elements(neko)
ans =

n2 
flg
n1 
	    

この様にMATLABとの互換性を考慮した場合、C言語の様にスマートには 行かないが、それでも、fgetsとsscanfを用いて処理が行える。

ファイルの更新とデータの追加の例

fopenの指定でファイルの更新(内容の入れ換え)とデータの追加が 行える。先ず、ファイルの更新は'w'を指定する。こうすると、既存の ファイルの内容は消去され、存在しない場合には、指定された名前の ファイルが新規に構築される。又、既存のファイルを利用する場合、 'a'を指定すると、既存のファイルの下に書き込む事が行える。

次に、例として与えられた行列データをフラグに沿って指定された ファイルに追加したり、置き換えるプログラムを示す。

function [err]=appendDATA(fname,mv,flg)
	err   = 0;
	% ファイル名の設定。
	vfname=[fname,'.vdt'];
	% フラグflg==0であれば上書き、それ以外は末尾にデータを
	% 追加する。
	if flg==0
		vfp = fopen(vfname,'w');
	else 
		vfp = fopen(vfname,'a');
	end;

	% データの書き込み
	[m,n]=size(mv);
	if m>0
		if flg==0
			fprintf(vfp,' %d ',mv(1,:));
			fprintf(vfp,'\n');
		end;
		for k=[2:m]
			fprintf(vfp,'%22.15e',mv(k,:));
			fprintf(vfp,'\n');
		end;
	else 
		err=1;
	end;
	fclose(vfp);
	    

この例では、ファイル名は修飾子".vdt"を抜いた型で与える。 又、ファイルの先頭行はカラムを分類する為に整数としており、 2行目以降が実際のデータで、行列mvもその様な形式となっている。 このデータ行の出力並びはprintf文の'%22.15e'で指定したもので、 この書式の設定はCやFORTRANのそれと同じ形式である。更に、この 処理を"%データの書き込み"とコメントした個所から下で行っている。

以上の様にOctaveのファイル処理では、命令やオプションがほぼC言語 に似た仕様となっている為、MATLABとの互換性を考慮しなければ、 C言語風に記述してCオプションを立てれば良い。更に、扱うデータを 数値行列にすれば非常に苦労も少なく、非常に気楽にファイル操作が 行える仕様となっている。しかし、MATLABとの互換性を考慮すると、 MATLABで書式指定を伴う処理では行単位で行われる為に、一行に文字と 数値が混在データファイルを扱う場合には工夫が必要となり、Octaveの 機能に頼るとMATLABで動作しないプログラムとなる可能性が非常に高い 事に注意が必要となる。


数式処理関係の目次に戻る

目次に戻る


Ponpoko
Last modified: Mon Sep 24 23:13:49 JST 2001