データ処理
コンピュータが得意とする分野にデータ処理があります。計算式自体はそれほど複雑ではないのですが、データ量が多いのが特徴です。そして、大抵の場合、データには計算に不要なものが含まれています。そのため、要らないデータを削除するなど計算以外の雑多な作業が発生します。
勝率の計算に引き分けは不要
rpnはデータ処理も得意な電卓です。スタックに積んだデータを操作することで、データを削除したり、順番を変えることができます。これによって、柔軟に計算できるようになるのです。
以下はあるリーグの各チーム毎の勝ち数、負け数、引き分け数です。それぞれ、1位から7位まで並んでいます。
31 19 10
29 21 10
24 25 11
23 27 10
19 32 9
15 30 15
data.txtにデータが格納されているとして、それぞれのチーム勝率を計算したいとします。勝率は勝ち数÷(勝ち数+負け数)で計算できますので、データの中で引き分け数が不要です。そこで、引き分け数を削除して、勝率計算するrpn式は以下のとおりです。
0.632653
0.62
0.58
0.489796
0.46
0.372549
0.333333
7チーム分の勝率が一気に計算されていますね。
勝率計算の過程
-tオプションは前回の説明どおりです。データはdata.txtからリダイレクトで受け取っていますので、今回はrpn式の「_ } . } + /」がdata.txtのそれぞれの行の最後に連結することになりますね。
例えば、以下のようにです。
ここで、「_」と「.」と「}」がスタック操作記号です。「_」はスタック最後の数値を削除します。例を挙げると以下のようになります。
1 2
スタックに「1 2 3」が積まれていましたが、3が削除されて「1 2」になっていますね。今回の場合は引き分け数を削除しています。
「}」はスタックに積まれた数値を向かって右に1つローテーション(回転)します。一番右端の数値は一番左端に移ります。具体的にrpn式で見るとすぐに分かります。
5 1 2 3 4
先に引き分けを削除しているので、残った勝ち数と負け数の数値が右に1つローテーションします。つまり、勝ち数と負け数がクルッとひっくり返ったことになります。
次の「.」はスタック最後の数値をコピーします。既に勝ち数と負け数がひっくり返っているので、勝ち数をコピーしたことになります。これで、負け数、勝ち数、勝ち数の三つの数値がスタックにあることになりますね。あとはもう一回右にローテーションして「+」と「/」で計算すれば勝率が求まります。
計算の経過が分かりづらい人は、次の解説を見てください。スタックの内容を表形式で示してあります。
======== ============== ==========================================
31 18 11 最初に「31 18 11」がスタック
_ 31 18 _で11が削除(引き分け数が削除)
} 18 31 }で右に1回ローテーション
. 18 31 31 .で31をコピー(勝ち数が2つに)
} 31 18 31 再度、}で右に1回ローテーション
+ 31 49 +で勝ち数と負け数を加算(全試合数)
/ 0.632653 /で勝ち数を全試合数で割って勝率
引き分け数を先に消しているので、残っているのは勝ち数と負け数だけです。2つしかスタックに数値が残っていないため、同じスタック操作記号の「u」を使っても結果は同じです。
uはスタックを逆順にひっくり返す操作を行います。
つまり、以下の2つのrpn式は同じ計算結果になります。
>rpn -t _ u . u + / <data.txt
ただし、2つ以上の数値がスタックにあるときは動作は異なります。以下の例で「{」「}」「u」の違いが理解できると思います。
2 3 1
>rpn 1 2 3 }
3 1 2
>rpn 1 2 3 u
3 2 1
実は-tは省略できる
今まで-tオプションを使ってrpn式を書いてきました。しかし、-tはよく使うのでなるべく手間をなくすために、-tを省略できるようになっています。以下の-t付きのrpn式を見てください。
この式は、次のように-tを省略することができます。
rpnは-tがなくてもrpn式だと判別できた時点で、-tオプションとして解釈するように設計されています。
もっと簡単な数式に変更
さて、-tオプションが省略できることが分かったのですが、肝心のrpn式の「_ } . } + /」は何だか難しいですよね。スタックのイメージが頭にないとなかなか理解できません。
これはデータを削除することと、勝率を計算することが一緒になっているから余計にそう感じるからです。また、レジスタを使わずにスタックだけで計算しようとしているのも分かりにくさの原因になっています。
そこで、rpn式を①と②に分けて書き直してみましょう。
①先に不要な引き分け数を削除
②勝ち数と負け数をそれぞれレジスタにして計算
①番目のrpn式は「_」だけです。②番目は引き分け数が既に削除されているとすると、「#l #w」でレジスタのlに負け数が、wに勝ち数が格納されます。その後、レジスタを参照することで「@w @w @l + /」で勝率計算できます。
この①と②のrpnを繋ぐと答えが出そうです。2つのrpn式を繋ぐにはパイプという仕組みを利用します。
パイプについては、rpn基礎のデータを転がして計算しようとdos入門のパイプに詳しい説明があります。
0.632653
0.62
0.58
0.489796
0.46
0.372549
0.333333
計算結果が出てきました。「_ } . } + /」と同じです。しかも、こちらのrpn式のほうがはるかに分かりやすいと思いませんか。
パイプ接続したときのスタックの動き
rpn式の動作をスタックの内容と共に追いかけてみましょう。
======== ============== ==========================================
31 18 11 最初に「31 18 11」がスタック
_ 31 18 _で11が削除(引き分け数が削除)
|
v ---+
(パイプ結合) | 31 18をデータとして次のrpnに渡す
| <--+
v
rpn式 スタックの内容 説明
======== ============== ==========================================
31 18 最初に「31 18」がスタック
#l 31 18をlレジスタに移動
#w 31をwレジスタに移動
@w 31 wレジスタから31をコピー
@w 31 31 wレジスタから31をコピー
@l 31 31 18 lレジスタから18をコピー
+ 31 49 31と18を足して49をスタック
/ 0.632653 31を49で割って勝率計算
パイプを使ったときのデータの流れ
次にrpnの動きを別の視点から説明します。今回のrpn式は、data.txtのデータがリダイレクトでrpnに渡されて、引き分け数の削除が行われます。勝ち数と負け数だけになったデータが再度、パイプで繋がったrpnに引き渡されます。rpnは勝ち数と負け数から勝率を計算して、計算結果のデータをそのまま画面に表示するという仕組みでしたね。
この動きを下の図のようにデータの流れとして表してみます。
+-----+ +---------------> (画面へ表示)
| | |
v | |
>rpn _ <data.txt | rpn #l #w @w @w @l + /
| ^
| |
+----------------------+
(引き分け数を削除して…)
2つのrpnがパイプによって、繋がれていることが分かります。計算結果は画面に流れていきますが、当然リダイレクトしてファイルに残しておくこともできます。以下がそのrpn式です。
tmpファイルに計算した勝率が格納されています。データの流れを図で示すと以下のようになります。
+-----+ +---------------+
| | | |
v | | v (tmpファイルに格納)
>rpn _ <data.txt | rpn #l #w @w @w @l + / >tmp
| ^
| |
+----------------------+
(引き分け数を削除して…)
確認のためにtmpファイルをtypeコマンドで表示してみましょう。
0.632653
0.62
0.58
0.489796
0.46
0.372549
0.333333
リダイレクトに続き、パイプを使ったrpnの計算スタイルはいかがでしたか。データの削除や勝率計算を別々のrpnで処理しながら、次のrpnにデータを引き渡していく姿が理解できると思います。