掲示板のUWSCスクリプトの重複コードをなくす
気が向いたので、スクリプトの重複コードをなくす過程を書いてみた。
けど、出遅れた気がするので、掲示板への投稿はやめた。
さてさて、スクリプトの書き方は個人の自由。
どう書いても期待通り動けば良いのです。
そして理解できるコードこそが、良いコードなのです。
でも、人間成長したいですよね。
簡素なものは速度や保守性が有利です。
ということで、DRY(Don't Repeat Yourself)信者が、スクリプトの繰り返しを排除する過程です。
元のスクリプト
dim K1000;dim K500;dim K100;dim K50 dim K1000D=5;dim K500D=5;dim K100D=4;dim K50D=2 引出金額 = input("金額を入れて下さい") //-------------------------------------------------------- if 引出金額 > 8000 or 引出金額 mod 50 > 0 or 引出金額 < 50 msgbox("現在の金種では支払いできない") exit endif //--------------------------------------------------------- ifb (引出金額>=1000) then for n=1 to 5 K1000=K1000+1 引出金額=引出金額-1000 ifb 引出金額<1000 then break endif next print "金種1000="+K1000 endif //--------------------------------------------------------- ifb (引出金額>=500) then for n=1 to 5 K500=K500+1 引出金額=引出金額-500 ifb 引出金額<500 then break endif next print "金種500="+K500 endif //--------------------------------------------------------- ifb (引出金額>=100) then for n=1 to 4 K100=K100+1 引出金額=引出金額-100 ifb 引出金額<100 then break endif next print "金種100="+K100 endif //--------------------------------------------------------- ifb (引出金額>=50) then for n=1 to 2 K50=K50+1 引出金額=引出金額-50 ifb 引出金額<50 then break endif next print "金種50="+K50 endif //--------------------------------------------------------- K1000D=K1000D-K1000;K500D=K500D-K500;K100D=K100D-K100;K50D=K50D-K50 残金=K1000D*1000+K500D*500++K100D*100+K50D*50 print K1000D+" , "+K500D+" , "+K100D+" , "+K50D+" "+残金
繰り返しは排除
金種毎に同じようなIF文とループがありますね。
これは繰り返し(Repeat)なので、DRY信者としては排除したくなります。
その前に、簡単なDIM文の修正
dim K1000;dim K500;dim K100;dim K50 dim K1000D=5;dim K500D=5;dim K100D=4;dim K50D=2
dim K1000, K500, K100, K50 dim K1000D=5, K500D=5, K100D=4, K50D=2
dimの繰り返しが排除されました。
文法上、カンマ区切りでの宣言が許されています。
可変配列でなければ、初期化も可能です。
続いて、if文の一致度合を見ます。
とりあえず、最初の二つ。
//--------------------------------------------------------- ifb (引出金額>=1000) then for n=1 to 5 K1000=K1000+1 引出金額=引出金額-1000 ifb 引出金額<1000 then break endif next print "金種1000="+K1000 endif //--------------------------------------------------------- ifb (引出金額>=500) then for n=1 to 5 K500=K500+1 引出金額=引出金額-500 ifb 引出金額<500 then break endif next print "金種500="+K500 endif
- 条件式の数値が1000と500
- 三行目のインクリメントする変数が、K1000とK500
- 引出金額の引く数値が1000と500
- breakへ至るifの条件式の数値が1000と500
- print文
FOR文でまとめあげるなら、1000と500の配列と、インクリメントする変数が二つ。
まずは変数
DIM 金種[] = 1000, 500 // 金種[0]=1000, 金種[1]=500 DIM 使用枚数[1] // 0と1の二つが使える。0がK1000、1がK500の想定
変数の準備ができたところで、二つのIF分をFOR文でラップしましょう。
まずは、FORで包むだけ。
FOR i = 0 TO LENGTH(金種) - 1 // 全ての金種でループする(0〜1のループ) ifb (引出金額>=1000) then for n=1 to 5 K1000=K1000+1 引出金額=引出金額-1000 ifb 引出金額<1000 then break endif next print "金種1000="+K1000 endif NEXT
続いて、差分を用意した変数に置き換える。
FOR i = 0 TO LENGTH(金種) - 1 ifb (引出金額>=金種[i]) then // 最初iが0で、金種[0]=1000。二回目iが1で、金種[1]=500 for n=1 to 5 使用枚数[i]=使用枚数[i]+1 引出金額=引出金額-金種[i] ifb 引出金額<金種[i] then break endif next print "金種" + 金種[i] + "=" + 使用枚数[i] endif NEXT
さて、100円の処理も併合してみよう。
FOR文にラップした中身との差は、
- 最初の条件式が、金種[i]と100
- nの終わりが5と4(入っている枚数の差)
- 加算するのが、使用枚数[i]とK100
- あとは値が、金種[i]と100
nの終わりの配列を用意すれば、くっつけられますね。
DIM 金種[] = 1000, 500, 100 DIM 使用枚数[2] DIM 枚数[] = 5, 5, 4 // nの終わり用 FOR i = 0 TO LENGTH(金種) - 1 // 金種が3種類になったので、0〜2のループ ifb (引出金額>=金種[i]) then for n=1 to 枚数[i] 使用枚数[i]=使用枚数[i]+1 引出金額=引出金額-金種[i] ifb 引出金額<金種[i] then break endif next print "金種" + 金種[i] + "=" + 使用枚数[i] endif NEXT
50円処理をくっつけるのは簡単ですね!
配列を変えるだけ。
DIM 金種[] = 1000, 500, 100, 50 DIM 使用枚数[3] DIM 枚数[] = 5, 5, 4, 2
そう、まとめた時の強みはこれ。
金種が増えた時、スクリプトのブロックをコピーしなくて済む。
まとめてないと、100円の処理をコピーして、数値を50にして、K100をK50にして、50円の処理作成。
どこか更新が漏れると、上手く動かない、、、。
例えば、100円を3枚、50円を3枚、さらに10円を5枚にする手間を考えてみてください。
どっちが楽か。そういうことなのです。
(元のスクリプトの方が楽なら、その人にはそれがあっているということです)
さて、完成させるために最後の部分を新しく用意した配列に変更します。
K1000D=K1000D-K1000;K500D=K500D-K500;100D=K100D-K100;K50D=K50D-K50 残金=K1000D*1000+K500D*500+K100D*100+K50D*50 print K1000D+" , "+K500D+" , "+K100D+" , "+K50D+" "+残金
うん、ごめん。これごちゃっとしてて私にはよくわからない。
どうやら、残った枚数とその合計金額を表示している模様。
ということで、適当に。
DIM 残金 = 0 PRINT "残ったのは" FOR i = 0 TO LENGTH(金種) - 1 枚数[i] = 枚数[i] - 使用枚数[i] PRINT 金種[i] + "円が" + 枚数[i] 残金 = 残金 + 金種[i] * 枚数[i] NEXT PRINT "合計金額は、" + 残金
ひとまず完成
DIM 金種[] = 1000, 500, 100, 50 DIM 使用枚数[3] DIM 枚数[] = 5, 5, 4, 2 引出金額 = input("金額を入れて下さい") //-------------------------------------------------------- if 引出金額 > 8000 or 引出金額 mod 50 > 0 or 引出金額 < 50 msgbox("現在の金種では支払いできない") exit endif FOR i = 0 TO LENGTH(金種) - 1 ifb (引出金額>=金種[i]) then for n=1 to 枚数[i] 使用枚数[i]=使用枚数[i]+1 引出金額=引出金額-金種[i] ifb 引出金額<金種[i] then break endif next print "金種" + 金種[i] + "=" + 使用枚数[i] endif NEXT DIM 残金 = 0 PRINT "残ったのは" FOR i = 0 TO LENGTH(金種) - 1 枚数[i] = 枚数[i] - 使用枚数[i] PRINT 金種[i] + "円が" + 枚数[i] 残金 = 残金 + 金種[i] * 枚数[i] NEXT PRINT "合計金額は、" + 残金
Linersさんのスクリプトに近づいてみよう
Linersさんのは、
dim 金種[] = 1000, 500, 100, 50 dim 枚数[] = 5, 5, 4, 2 入金 = input("金額を入れて下さい(<=8000)") if 入金 > 8000 or 入金 mod 50 > 0 or 入金 < 50 msgbox("現在の金種では支払いできない") exit endif for i = 0 to length(金種) -1 最小枚数 = int(入金 / 金種[i]) if 最小枚数 > 枚数[i] then 最小枚数 = 枚数[i] if 最小枚数 > 0 then print format(金種[i], 4) + "円 が " + 最小枚数 + "枚" 入金 = 入金 - 金種[i] * 最小枚数 next msgbox("確認")
大きな違いは、使用枚数の配列がないのと、forループの中身。
まずは、使用枚数の配列について。
残金の表示等をしないのであれば、使用枚数の配列はいらないのです。
試しに削ってみましょう。
DIM 金種[] = 1000, 500, 100, 50 DIM 枚数[] = 5, 5, 4, 2 引出金額 = input("金額を入れて下さい") //-------------------------------------------------------- if 引出金額 > 8000 or 引出金額 mod 50 > 0 or 引出金額 < 50 msgbox("現在の金種では支払いできない") exit endif FOR i = 0 TO LENGTH(金種) - 1 ifb (引出金額>=金種[i]) then 使用枚数 = 0 for n=1 to 枚数[i] 使用枚数=使用枚数+1 引出金額=引出金額-金種[i] ifb 引出金額<金種[i] then break endif next print "金種" + 金種[i] + "=" + 使用枚数 endif NEXT
使用枚数という配列から、使用枚数という変数に変えても、なんら問題ありません。
次、nのループについて。
使用枚数 = 0 for n=1 to 枚数[i] 使用枚数=使用枚数+1 引出金額=引出金額-金種[i] ifb 引出金額<金種[i] then break endif next
これだけの処理でやっていることはなんでしょう?
使用枚数の算出と、引出金額の減算ですね。
じゃ、ちょこっと変えてみましょう。
使用枚数 = 0 for n=1 to 枚数[i] 使用枚数=使用枚数+1 ifb 引出金額<金種[i]*(使用枚数+1) then break endif next 引出金額=引出金額-金種[i]*使用枚数
引出金額の減算が外に出たので、for文は使用枚数の算出だけになりました。
使用枚数の算出、ループでやることでしょうか?
否です。
計算で求まります。
例えば、4000円用意するのに1000円札は何枚必要?
4枚ですね。
そう割ればいいのです。
使用枚数 = 引出金額 / 金種[i]
でも待ってください。
4200円用意するのに1000円札は何枚必要?
4枚と、200円は硬貨の出番ですね。
でも上の式だと、4.2枚とか言われます。
はい、切り捨てましょう。
使用枚数 = INT(引出金額 / 金種[i])
これでOK?
いえいえ、まだです。
6000円用意するのに1000円札は何枚必要?
6枚、、、でも、5枚しかないですね。
残り1000円は硬貨の出番。
ということで、その条件も入れましょう。
使用枚数 = INT(引出金額 / 金種[i]) IF 使用枚数 > 枚数[i] THEN 使用枚数 = 枚数[i]
これでOKです。
できあがり
DIM 金種[] = 1000, 500, 100, 50 DIM 枚数[] = 5, 5, 4, 2 引出金額 = input("金額を入れて下さい") //-------------------------------------------------------- if 引出金額 > 8000 or 引出金額 mod 50 > 0 or 引出金額 < 50 msgbox("現在の金種では支払いできない") exit endif FOR i = 0 TO LENGTH(金種) - 1 ifb (引出金額>=金種[i]) then 使用枚数 = INT(引出金額 / 金種[i]) IF 使用枚数 > 枚数[i] THEN 使用枚数 = 枚数[i] 引出金額=引出金額-金種[i]*使用枚数 print "金種" + 金種[i] + "=" + 使用枚数 endif NEXT
なんだ、Linersさんのスクリプトか、、、というくらい似てますね。
FOR文やIF文によるネストは浅い方が良いという思想もあります。
それを取り入れると、ほぼLinersさんのスクリプト。
または、以下のようにCONTINUEにする手もあります。
FOR i = 0 TO LENGTH(金種) - 1 if (引出金額 < 金種[i]) then continue 使用枚数 = INT(引出金額 / 金種[i]) IF 使用枚数 > 枚数[i] THEN 使用枚数 = 枚数[i] 引出金額=引出金額-金種[i]*使用枚数 print "金種" + 金種[i] + "=" + 使用枚数 NEXT
あと、DRY信者的に気になるのは、
- 支払できないケースの条件式
でしょうか。
ま、この辺はわかり易さやメモリー使用量とのバランスです。