作成開始日 2007.09.27
最終更新日 2014.05.27
DELTAG: PROCEDURE; PARSE ARG st; DO While (POS('<',st)>0)|(POS('>',st)>0) p=POS('<',st) q=POS('>',st); IF q=0 THEN q=LENGTH(st); /* <………| ⇒ <………> */ IF p=0 THEN p=1; /* |………> ⇒ <………> */ IF p>q THEN p=1; /* |…><…| ⇒ <…><…| */ st=SUBSTR(st,1,p-1)||SUBSTR(st,q+1) END RETURN stあくまでも行単位の処理なので、タグが複数行にまたがり、尚且つ行内に「<」も「>」も含まれていない場合はお手上げ(スクリプトやcss)。逆に、ソースコードやスクリプトに「<」や「>」が含まれていると、タグと看做してしまう。この辺りは、このルーチンに掛ける前に判断して分岐処理をしないとダメだろう。
そこで、新データリストと旧データリストの類似性が高いことを前提に、もっと効率的な差分算出の方法を考える。類似性が高いというのは、万単位のリストのうち、追加・削除されたデータはせいぜい百単位程度で、リストの並び順も基本的に同じであるような場合を指す。もし、大量のファイルの追加・削除があったなら、総当たり比較なり、完全な作り直しなりが必要になるし、リストの並び順が完全ランダムであるなら、効率的な差分算出の手掛かりがなくなる。
まず、新ディレクトリ・リストを基準に処理を始める。 新ディレクトリ・リストにはそもそも削除されたファイルは含まれないので、 3種のファイルのうち、ドロップに関してはこの時点で処理が終っている。 問題は、新規追加分と既存分の判別である。
基本的な考え方は、総当たり比較ではなく、
一つは、新4番に一致するデータが、旧4番よりも前にある可能性はないのか? という点。 これは基本的にない。というか、ないことを前提にしたアルゴリズムである。 もし、イレギュラーな事態が発生して(手作業でリストをいじるなど)、 該当旧データが前に移動していた場合には発見できなくなるわけだが、 その場合は諦めて、追加扱いでCRCを作り直せば良い。 そのオーバーヘッドは、比較を頭からやり直すオーバーヘッドよりも遥かに小さい (4番や5番なら問題はないが、同じ事態が1万番や2万番でも起きうるので)。 それに、そんなことは滅多に起きない。
二つ目は、新4番と旧4番が不一致の段階で、即追加データと判断することは できないのか?という点。これはできない。 リストの不一致は追加だけでなく、削除でも起きるから。 仮に、新4番が既存データで、旧4番は削除されたデータの場合でも、 同じように同番でのデータの不一致が起きる。 しかし、この場合、新4番は旧5番と一致する筈である。 この可能性がある以上、「不一致即追加データ」という判断はできず、 比較を続ける必要がある。
この場合重要なのは、追加や削除がデータ番号のズレを累積していく点。 最初に、同番比較が基本と言ったが、ズレが生じれば、同番ではなく、 累積されたズレを考慮した番号での比較が必要になる。
Do n=1 to newMax; newValue.n=''; End; /* 値の初期化 */ m0=1 Do n=1 to newMax /* newMax、oldMaxは新旧データの数 */ Do m=m0 to oldMax /* m0からスタートするのがミソ */ IF newData.n=oldData.m THEN /* 既存データと一致した場合 */ Do newValue.n=oldValue.m /* 既存データの値を流用 */ m0=m+1 /* 旧データの比較開始点を次へ */ Leave; End; End; End;この模擬コードでは、既存データには旧データと同じValueが与えられるが、 追加データのValueは空のままである。 逆に言えば、Valueがそのまま追加データのフラグになっているので、 Valueが空のデータのみ新たに処理すれば良い。
【注意】外部サブルーチンは内部サブルーチンよりもかなり遅くなる。使用頻度の高いものは要再考。
変数は原則的に全てローカライズされる(特殊なものは除く)。 引数の受け取りはPARSE ARGで、戻り値はRETURNで指定すればよい。 なお、配列の受け渡しは不可能だが、キューで仲介する方法はある。
たとえば、外部ルーチン側でarray.1〜array.100という配列データを作成/処理した場合;
DO n=1 to 100 QUEUE array.n ENDとして、配列データをキューに入れる。 これを受け取るメインルーチン側では;
DO n=1 to 100 PARSE PULL array.n ENDとして取り出せば良い。キューはプログラム間(と言うよりシステム全体)で共有できる。
なお、プログラムの不正終了などでキューにデータが残った場合、 自動的なクリアは行われないので、 事前に次のような方法で初期化しておくと良い。
q=QUEUED(); DO n=1 TO q; PULL dmy; END;
mySub: Procedure Expose myArray.みたいな感じで、配列名のあとに「.」を付ければ良い。
MemUnit: Procedure ; mem=arg(1) unitSt='KMGT' Do m=1 to 4 mem=mem/1000 if mem<1 then leave End mem=mem*1000 mem=left(mem,3)||' '||substr(unitSt,m-1,1)||'B' Return mem End;
PARSE VALUE SysCurPos() WITH raw col Do n=1 to nMax IF n//100=0 THEN /* この場合は100回おきに表示 */ DO rc=SysCurPos(raw,col) CALL CHAROUT ,'-proceeding ... 'n '/' nMax END ……… ……… ……… End
st=TRANSLATE(st,'abcdedfghijklmnopqrstuvxwyz','ABCDEDFGHIJKLMNOPQRSTUVXWYZ')でも、これではカッコ悪いので、もう少しスマートにすると;
st=TRANSLATE(st, XRANGE('a','z'), XRANGE('A','Z'))文字列が半角英数字のみから構成されているなら、ビット演算で;
st=BITOR(st,,'20'x)とするのが簡単。ただし、記号は一部化けるので要注意(下記の例では、末尾の3つの記号が化けている)。
SAY BITOR('ABCabcXYZ123*+-/._^\',,'20'x) /* → abcabcxyz123*+-/.~| */
【サンプルスクリプト】
/* LC.CMD : ファイル名小文字化スクリプト */ /* カレントディレクトリ内のファイル名をすべて小文字化する */ Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs' Call SysLoadFuncs rc=sysfiletree('*','file','FO') do n=1 to file.0 fn0=file.n fn1=TRANSLATE(fn0, XRANGE('a','z'), XRANGE('A','Z')) fn1=FILESPEC('N',fn1) '@REN' fn0 fn1 say fn1 end; exit;
たとえば、ロング名を持つJpeg画像のファイル名およびオブジェクト名を、右から8文字に切り詰めるには、次のようにする。
/* オブジェクト名変更 */ Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs' Call SysLoadFuncs rc=SysFileTree('*.jpg',file,'OF'); Do n=1 to file.0 fname=filespec('N',file.n); parse var fname body '.' ext newbody=right(body,8) /* ロング名を右から8文字に切り詰める */ newfname=newbody||'.'||ext; say newfname dataStr='title='||newfname rc=SysSetObjectData(file.n,dataStr); End;ただし、この方法は目茶苦茶時間が掛かる。何をやっているのか知らないが…。で、外道な方法としては、RENで物理名を変えて、eautil /sで拡張属性を削除する、という方法がよいかな。
【構文】rc=STREAM('ファイル名','c','query exists') 【戻値】存在すればrcにフルパス付きファイル名を返す 【用例】rc=STREAM('E:\ABC\file.txt','c','query exists')
注 意 |
この方法はディレクトリの存否確認には使えない。ディレクトリはストリームにならないため。 ディレクトリの有無の確認にはSysFileTreeを使う。 |
また、STREAM関数を使えば、ファイルのタイムスタンプやファイルサイズをチェックすることもできる。
【構文】rc=STREAM('ファイル名','c','query datetime') 【戻値】rcにタイムスタンプを返す 【構文】rc=STREAM('..\file.txt','c','query size') 【戻値】rcにファイルサイズを返す