bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

流水算を解く(TypeとTuple)

1.流水算とは

小学校の頃を思い出して、今回は、流水算。川を上り下りする船の所要時間を知って、川の流れの速さと船の静水時の速さ(水の流れがないときに船が進む速さ)を解く問題である。この問題には、前提があって、次の式が成り立っていると想定している。
川を下るときの速さ=船の静水時の速さ+川の流れの速さ
川を上るときの速さ=船の静水時の速さ-川の流れの速さ
但し、川は静かに流れていて、その速度は変わることはなく、また、当然のことだが、船の静水時の速さは川の流れの速さより速いとする。

算数の問題を解くときは、なるべく、図を用いて、その本質を理解するのが良い。そこで上の式を図で示すと次のようになる。図では船の静水時の速さを青で、川の流れの速さを茶で、川を下るときの速さと川を上るときの速さを黄色で示してある。
f:id:bitterharvest:20141001105611p:plain
川を下るときの速さと川を上るときの速さを加えると下図になる。
f:id:bitterharvest:20141001105627p:plain
この図から、これは船の静水時の速さを2倍したものであることが分かる。

川を下るときの速さから川を上るときの速さを引くと下図になる。
f:id:bitterharvest:20141001105647p:plain
この図から、これは川の流れの速さを2倍したものであることが分かる。

2.Haskellで実現する

それでは、流水算を解くこととする。流水算では、通常、川の長さと川を下るときに要した時間と川を上るときに要した時間が与えられ、船の静水時の速さと川の流れの速さを求める。
そこで、流水算をHaskellで解くことを考える。まず、川の長さを表す型シノニムLengthと所要時間を表す型シノニムTimeと速度を表す型シノニムSpeedを次のように用意する。なお、川の長さはKmで、所要時間は時間で与えられるものとし、端数はないものとする。(型シノニムは新しい型を用意したわけではなく、単なる言いかえ。これは、プログラムを見やすくするときの一つのテクニック)

type Length = Int
type Time = Int
type Speed = Float

次に、船の静水時の速さと川の流れの速さを得る関数runningWaterを定義する。入力は川を上るときに要した時間と川を下るときに要した時間なので、入力の型変数はTimeである。出力は船の静水時での速度と川の流れの速度の対(Haskellではタプルで実現)とすると、出力の型変数はSpeedのタプルである。これを用いてrunningWaterの型シグネチャを定義すると次のようになる。

runningWater :: Length -> Time -> Time -> (Speed, Speed)

Haskellでプログラムを書いているとき、型シグネチャを定義できれば、80-90%程度プログラムは完成したものと思っていい。後は関数の本体を定義すればよい。川の長さをriver、川を下るときにかかった時間downと川を上るときにかかった時間upを用いて定義する。
なお、整数同士で割り算をすると整数が得られる。ここでは小数を得たい。そこで、整数がどのような型にもなれるように、fromIntegralを用いて変換する。このようにしておけば、式の中で型が決まることになる。それでは、先ほどの図を用いてプログラムをその通り素直に書くと次のようになる。

runningWater river down up =
  (doubleShipSpeed / 2, doubleRiverSpeed / 2)
  where
    downSpeed        = fromIntegral river / fromIntegral down
    upSpeed          = fromIntegral river / fromIntegral up
    doubleShipSpeed  = downSpeed + upSpeed
    doubleRiverSpeed = downSpeed - upSpeed

3.プログラム全体

プログラムの全体を示すと次のようになる。

type Length = Int
type Time = Int
type Speed = Float

runningWater :: Length -> Time -> Time -> (Speed, Speed)
runningWater river down up =
  (doubleShipSpeed / 2, doubleRiverSpeed / 2)
  where
    downSpeed        = fromIntegral river / fromIntegral down
    upSpeed          = fromIntegral river / fromIntegral up
    doubleShipSpeed  = downSpeed + upSpeed
    doubleRiverSpeed = downSpeed - upSpeed

この関数を用いれば、川の長さが36Km、下りに要した時間が2時間、上りに要した時間が3時間の場合には、船の静水時の速度は毎時15 Km、川の流れの速度は毎時3 Kmを得ることができる。