平均値計算の負担を減らす

 ここ2、3年間、私は、仕事や趣味以外の目的で、つまり、健康管理のために毎日朝晩、血圧の測定をしています。そして、その測定結果を、数値記入式の家庭用血圧記録ノートに記入しています。そのノートには、2週間ごとにそれまでの測定値から平均値を求めて、それを記入する欄があります。2か月ごとに、かかりつけのお医者さんに通院するおりに、そのノートを持っていきます。その時までに、2週間ごとの平均値(朝と晩の最低・最高血圧それぞれの平均値4つ)を、2か月分、計算しておかなくてはなりません。
 2か月ごとにまとめてやっている、その平均値計算が私には負担でした。最初は、筆算でやっていたのですが、扱う数値が多いため、計算間違いをしやすく、その検算に時間をより多く費やす羽目になってしまいました。筆算の代わりに、卓上電卓や、パソコン上の電卓アクセサリを利用するようになりました。けれども、やはり数字や演算記号の打ち間違えはあって、計算し直しはなくなりませんでした。
 そこで、そうした計算間違いや、数字などの打ち込みミスをなるべく減らすために、ウィンドウズOS上で簡単に動くアクセサリ(今風に言えば、アプリ)を作ってみることにしました。コンピュータ言語処理系のコンパイラを使うと本格的なプログラム開発になってしまうので、今回はHTMLアプリケーションとVBスクリプトという、いわゆる簡易言語を使って、そのようなアプリケーション・プログラムを作ってみることにしました。このHTML(Hyper Text Markup Language の略)やVBスクリプトは、最近のウィンドウズOSに標準に付いていて、手軽に使えます。ただし、具体的にそれらを利用するためには、Dynamic HTMLやVBスクリプトのポケット・リファレンス、あるいは、スタイルシート辞典などの書籍から必要な知識を引いてくる必要があると思います。私の場合も、これらの知識は(株)アンクの書籍から入手して、また、HTMLアプリケーションの知識はインターネット上から取り入れました。

<STYLE TYPE="text/css">

  BODY  { background-color:rgb(80%,70%,70%); }
  SPAN  { font-size:12pt; height:20pt; padding:4pt; margin:3pt 0pt; }
  .wbtn { background-color:white; border-bottom: thin solid gray;
          border-right: thin solid gray; }
  TEXTAREA { font-size:14pt; scroll:none; ime-mode:disabled; }

</STYLE>

<HTA:Application Id=oHTA Scroll=no  Contextmenu=no />


<BODY scroll=No>
  <TEXTAREA name=Nums cols=34 rows=5 wrap=hard ></TEXTAREA><P>
  <SPAN CLASS=wbtn id=Btn1 >平均値は?</SPAN>
  <SPAN>  </SPAN>
  <SPAN CLASS=wbtn id=Btn2 >数値のクリア</SPAN><BR>
  <SPAN id=Mes ></SPAN>
</BODY>


<SCRIPT language="VBScript">
Option Explicit


Dim WSHShell
Set WSHShell = CreateObject("WScript.Shell")

Dim Chkstr, VList, VLcnt, Average


Call ResizeTo(400, 270)

Document.Title = "入れた数値を均すんです"

Chkstr = "0123456789+- " & vbCR & vbLF


WSHShell.SendKeys "{TAB}"


Sub Nums_OnClick

    If Left(Btn1.InnerText, 5) = "平均値は、" Then

       Btn1.InnerText = "平均値は?"

    End If

End Sub


Sub Btn1_OnClick

    If Nums.value = "" Then

       Mes.InnerText = "数値が入っていません。"

       WSHShell.SendKeys "{TAB}{TAB}"


    ElseIf ValsCheck(Nums.value) Then

       Mes.InnerText = ""

       Call MakeVList(Nums.value)
       Average = CalcAverage(VList, VLcnt)

       Btn1.InnerText = "平均値は、" & Average & " です。"

    Else

       Mes.InnerText = "数値以外が入っていて計算できません。"

    End If

End Sub


Sub Btn2_OnClick

    Nums.value  = ""
    Mes.InnerText = ""
    Btn1.InnerText = "平均値は?"

    WSHShell.SendKeys "{TAB}"

End Sub


Private Function ValsCheck(bufstr)
    Dim flg, bsz, idx, ret

    flg = True
    bsz = LenB(bufstr)

    For idx = 1 to bsz

        ret = InStrB(1, Chkstr, MidB(bufstr, idx, 1))

        If ret = 0 Then

           flg = False
           Exit For

        End If

    Next

    ValsCheck = flg

End Function


Private Sub MakeVList(bufstr)
    Dim dstr, idx, debug

    dstr = Replace(bufstr, " ", "+")
    dstr = Replace(dstr, "-", "+-")
    dstr = Replace(dstr, vbCR, "+")
    dstr = Replace(dstr, vbLF, "+")

    VList = Split(dstr, "+")
    VLcnt = UBound(VList)

    For idx = VLcnt to 0 Step -1

        If VList(idx) = "" Then

           If idx < VLcnt Then

              Call PackVList(idx)

           End If

           VLcnt = VLcnt - 1

        End If
    Next

End Sub


Private Sub PackVList(pos)
    Dim idx

    For idx = pos to VLcnt-1

        VList(idx) = VList(idx+1)

    Next

End Sub


Private Function CalcAverage(VList, VLcnt)
    Dim s, idx

    s = 0

    For idx = 0 to VLcnt

        s = s + Eval(VList(idx))
    Next

    CalcAverage = Round(s / (VLcnt + 1) + 0.01)

End Function


</SCRIPT>

 これが、今回私が作ったプログラムです。キーボードから打ち込んだ数値の平均値を自動的に求めて、画面に表示する簡単なものです。
  CalcAverage = Round(s / (VLcnt + 1) + 0.01)
という行で、平均値が求まります。VBスクリプトに用意されているRound関数で、数値の合計sを数値の個数 (VLcnt + 1)で割り算したものを小数点以下で四捨五入して、血圧の平均値(整数値)を計算します。その時、 Round関数が2.5や4.5などの数値を2や4にしてしまう場面が見つかりました。私が思うに、それはコンピュータの浮動小数点の表現によって微小な誤差が生じるためと思われます。すなわち、小数2.5はコンピュータ内では、2.499999...と本当は表されています。そこで、小数点第1位で四捨五入すると、2.5は2となってしまいます。それを正しい平均値となるように補正するためには、2.49999...の小数点第2位に微小な値を与えてあげれば、それは2.5以上の値に補正されて、その計算は2.5が3になります。ゆえに、平均値の小数点第1位を四捨五入する前に、その小数点第2位に何らかの微小な値(0.01)を加えておきます。
 しかしながら、この手のプログラムを作る上で問題になるのは、そうした平均値の計算方法よりも、数字や数式の打ち込み間違いを減らす対策をどうするか、ということです。それに関するいろんな方策が考えられますが、私が選んだ方策は次の通りです。1.平均値を出したい複数の数値を、TEXTAREAタグの中に一括でキー入力できるようにする。2.TEXTAREAタグの中では、半角文字だけが打ち込めるように設定しておく。3.平均値の計算に必要な数値は、0から9の数字と、スペース、+と−の演算記号、改行コード(CRLF)のみのキー入力とする。それ以外の英字などがキーで打ちこまれていた場合は、平均値計算をする前にエラーにする。4.何も数値が打ち込まれない場合は、平均値計算ができないのでエラーにする。
 以上1から4までの方策に従って、キーボードからの数値の取り込みチェックを行います。それらのチェックが全てOKとなった場合には、”11+23 66 +306 ”とか”5 24 6 78 345 29”というふうな半角文字・数字の文字列として、プログラムの中に取り込みます。VBスクリプトは文字列操作関数が充実しているので、キーボードから取り込んだ数値が文字列だとその後の加工処理がしやすいというメリットがあります。
 数値と数値は、スペースや+や−や改行コードで区切れます。しかも、それらは、数値と数値の間でいくつも使えるものとします。そうしたフリー・フォーマットは、数値の取り出し方法が困難になると思われるかもしれません。しかし、既存の文字列操作関数のいくつかを組み合わせて、それらを駆使してみると、そうした数値の取り出しがたやすくできるようになりました。すなわち、文字列で表されるそれぞれの数値を、リスト配列化することができます。そのリスト配列の数値データを一つ一つ、Eval関数でバイナリ化して足し込んでいけば、それらの数値の合計sが算出されます。また、そうした一連の処理の途中で、それらの数値が何個であるかを求めることもできます。すなわち、数値の合計を割るところのその個数を、キーボードから数値を打ち込む人間の側が憶えていなくてもいいことになります。それらの数値の個数は、私の作ったプログラムに従ってコンピュータが検知します。
 つまり、必要な数値と、それらを区切るスペースや改行をキーボードから打ち込んで、「平均値は?」という、ウィンドウ内のボタンをマウスでクリックするだけで、「平均値は、34 です。」というふうに結果が出てくる、という仕組みにしてみました。さらに、「数値のクリア」ボタンを用意しておいて、それをマウスでクリックすると、数値をキーボードから入れる前の初期状態に戻れるようにしました。
 プログラムを以上のような仕掛けにしてみると、「平均値は?」ボタンを押して、もしも変な平均値が出てきたならば、キーボードから打ち込んだ数値の列をチェックして、おかしな箇所を直して、再度「平均値は?」ボタンを押して、正しい結果を求めます。電卓を利用するよりも、はるかに使いやすくなったはずです。
 最後に、上に紹介したプログラムを、オブジェクトやクラスを使ったプログラムに書き替えてみました。それを、今回のブログ記事の終わりに載せておきました。興味のある方は、何かの参考にしてください。ざっと見た感じ、処理の流れやデータ構造が少しだけスッキリしたかもしれません。これぞ、オブジェクト指向プログラミングの成果と申しましょうか、コンピュータのプログラミングが昔よりもさらに簡単になったと言えましょう。(もちろん、そうした長所がある反面、そのことには短所も当然ありました。プログラムがわかりやすくなった反面、インターネットのセキュリティには弱くなってしまったそうです。でも、それは仕方がなかったのかもしれません。)
 ちなみに、お気づきかもしれませんが、プログラムの全体をHTMLタグで囲っていません。これだと、ウィンドウズXPでは動作しません。ですが、ウィンドウズ7/8/10のバージョンのHTMLアプリケーションでは、HTMLタグを使わないこのような書式が可能になっています。

<STYLE TYPE="text/css">

  BODY  { background-color:rgb(80%,70%,70%); }
  SPAN  { font-size:12pt; height:20pt; padding:4pt; margin:3pt 0pt; }
  .wbtn { background-color:white; border-bottom: thin solid gray;
          border-right: thin solid gray; }
  TEXTAREA { font-size:14pt; scroll:none; ime-mode:disabled; }

</STYLE>

<HTA:Application Id=oHTA Scroll=no  Contextmenu=no />


<BODY scroll=No>
  <TEXTAREA name=Nums cols=34 rows=5 wrap=hard ></TEXTAREA><P>
  <SPAN CLASS=wbtn id=Btn1 >平均値は?</SPAN>
  <SPAN>  </SPAN>
  <SPAN CLASS=wbtn id=Btn2 >数値のクリア</SPAN><BR>
  <SPAN id=Mes ></SPAN>
</BODY>


<SCRIPT language="VBScript">
Option Explicit


Dim WSHShell
Set WSHShell = CreateObject("WScript.Shell")

Dim VL
Set VL = New AverageCalculation

Call ResizeTo(400, 270)

Document.Title = "入れた数値を均すんです"

WSHShell.SendKeys "{TAB}"



Sub Nums_OnClick

    If Left(Btn1.InnerText, 5) = "平均値は、" Then

       Btn1.InnerText = "平均値は?"

    End If

End Sub


Sub Btn1_OnClick

    If Nums.value = "" Then

       Mes.InnerText = "数値が入っていません。"

       WSHShell.SendKeys "{TAB}{TAB}"


    ElseIf VL.CheckOK(Nums.value) Then

       Mes.InnerText = ""

       VL.MakeList = Nums.value
       Btn1.InnerText = "平均値は、" & VL.CalcAverage & " です。"

    Else

       Mes.InnerText = "数値以外が入っていて計算できません。"

    End If

End Sub


Sub Btn2_OnClick

    Mes.InnerText = ""

    Nums.value  = ""
    Btn1.InnerText = "平均値は?"

    WSHShell.SendKeys "{TAB}"

End Sub


Class AverageCalculation
    Private Chkstr, List, Lcnt

    Private Sub Class_Initialize

        Chkstr = "0123456789+- " & vbCR & vbLF

    End Sub


    Public Function CheckOK(bufstr)
        Dim flg, bsz, idx, ret

        flg = True
        bsz = LenB(bufstr)

        For idx = 1 to bsz

            ret = InStrB(1, Chkstr, MidB(bufstr, idx, 1))

            If ret = 0 Then

               flg = False
               Exit For

            End If

        Next

        CheckOK = flg

    End Function


    Public Property Let MakeList(bufstr)
        Dim dstr, idx, debug

        dstr = Replace(bufstr, " ", "+")
        dstr = Replace(dstr, "-", "+-")
        dstr = Replace(dstr, vbCR, "+")
        dstr = Replace(dstr, vbLF, "+")

        List = Split(dstr, "+")
        Lcnt = UBound(List)

        For idx = Lcnt to 0 Step -1

            If List(idx) = "" Then

               If idx < Lcnt Then

                  Call PackList(idx)

               End If

               Lcnt = Lcnt - 1

            End If
        Next

    End Property


    Private Sub PackList(pos)
        Dim idx

        For idx = pos to Lcnt-1

            List(idx) = List(idx+1)

        Next

    End Sub


    Public Property Get CalcAverage
        Dim s, idx

        s = 0

        For idx = 0 to Lcnt

            s = s + Eval(List(idx))
        Next

        CalcAverage = Round(s / (Lcnt + 1) + 0.01)

    End Property

End Class


</SCRIPT>