掲示板の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
  1. 条件式の数値が1000と500
  2. 三行目のインクリメントする変数が、K1000とK500
  3. 引出金額の引く数値が1000と500
  4. breakへ至るifの条件式の数値が1000と500
  5. print文

FOR文でまとめあげるなら、1000と500の配列と、インクリメントする変数が二つ。
まずは変数

DIM 金種[] = 1000, 500  // 金種[0]=1000, 金種[1]=500
DIM 使用枚数[1] // 01の二つが使える。0がK1000、1がK500の想定


変数の準備ができたところで、二つのIF分をFOR文でラップしましょう。
まずは、FORで包むだけ。

FOR i = 0 TO LENGTH(金種) - 1   // 全ての金種でループする(01のループ)
  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文にラップした中身との差は、

  1. 最初の条件式が、金種[i]と100
  2. nの終わりが5と4(入っている枚数の差)
  3. 加算するのが、使用枚数[i]とK100
  4. あとは値が、金種[i]と100

nの終わりの配列を用意すれば、くっつけられますね。

DIM 金種[] = 1000, 500, 100
DIM 使用枚数[2]
DIM 枚数[] = 5, 5, 4    // nの終わり用

FOR i = 0 TO LENGTH(金種) - 1   // 金種が3種類になったので、02のループ
  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信者的に気になるのは、

  • 支払できないケースの条件式

でしょうか。
ま、この辺はわかり易さやメモリー使用量とのバランスです。