第9回オフラインリアルタイムどう書くの問題をF#で

参考問題を解くつもりが実問題を解いてしまったようだ。

```fsharp:bus.fs
type Age = Infant | Child | Adult
type Rate = Normal | Commuter | Welfare
type Fare = Age * Rate * int

let UNIT = 10
let roundup u f = (f / u * u) + (if f % u = 0 then 0 else u)
let applyAge (a,r,i) =
  match a with
  | Adult -> (a,r,i)
  | _     -> (a,r,roundup UNIT (i/2))
let applyRate (a,r,i) =
  match r with
  | Normal   -> (a,r,i)
  | Commuter -> (a,r,0)
  | Welfare  -> (a,r,roundup UNIT (i/2))
let rec free n ps =
  match (n, ps) with
  | (0, _)           -> ps
  | (_, [])          -> []
  | (_, (a,r,_)::xs) -> (a,r,0)::free (n-1) xs
let partitionByAge ps =
  let folder p (al,cl,il) =
    match p with
    | (Adult, _,_) -> (p::al,   cl,   il)
    | (Child, _,_) -> (   al,p::cl,   il)
    | (Infant,_,_) -> (   al,   cl,p::il)
  List.foldBack folder ps ([],[],[])
let fare ps =
  let (al,cl,il) =
    ps
    |> List.map (applyAge >> applyRate)
    |> partitionByAge
  let il2 =
    il
    |> List.sortWith (fun (_,_,x) (_,_,y) -> compare y x)
    |> free (2 * List.length al)
  List.append al << List.append cl <| il2
let parse (str:string) =
  let toA c =
    match c with
    | 'A' -> Adult
    | 'C' -> Child
    | 'I' -> Infant
    | _   -> failwith "invalid input"
  let toR c =
    match c with
    | 'n' -> Normal
    | 'p' -> Commuter
    | 'w' -> Welfare
    | _   -> failwith "invalid input"
  let colon = str.IndexOf(':')
  let f = System.Int32.Parse(str.Substring(0, colon))
  str.Substring(colon+1).Split([|','|])
  |> Array.map (fun s -> (toA (s.Chars 0), toR (s.Chars 1), f))
  |> Array.toList
let calc = parse >> fare >> List.sumBy (fun (_,_,f) -> f)

(* for test *)

type TestResult = Success | Failure

let test target expected =
  let actual = calc target
  printfn "%A" (if expected = actual then Success else Failure)

[<EntryPoint>]
let main args =
  test "210:Cn,In,Iw,Ap,Iw" 170
  test "220:Cp,In" 110
  test "230:Cw,In,Iw" 240
  test "240:In,An,In" 240
  test "250:In,In,Aw,In" 260
  test "260:In,In,In,In,Ap" 260
  test "270:In,An,In,In,Ip" 410
  test "280:Aw,In,Iw,In" 210
  test "200:An" 200
  test "210:Iw" 60
  test "220:Ap" 0
  test "230:Cp" 0
  test "240:Cw" 60
  test "250:In" 130
  test "260:Cn" 130
  test "270:Ip" 0
  test "280:Aw" 140
  test "1480:In,An,In,In,In,Iw,Cp,Cw,In,Aw,In,In,Iw,Cn,Aw,Iw" 5920
  test "630:Aw,Cw,Iw,An,An" 1740
  test "340:Cn,Cn,Ip,Ap" 340
  test "240:Iw,Ap,In,Iw,Aw" 120
  test "800:Cw,An,Cn,Aw,Ap" 1800
  test "1210:An,Ip,In,Iw,An,Iw,Iw,An,Iw,Iw" 3630
  test "530:An,Cw,Cw" 810
  test "170:Aw,Iw,Ip" 90
  test "150:In,Ip,Ip,Iw,In,Iw,Iw,In,An,Iw,Aw,Cw,Iw,Cw,An,Cp,Iw" 580
  test "420:Cn,Cw,Cp" 320
  test "690:Cw,In,An,Cp,Cn,In" 1220
  test "590:Iw,Iw,Cn,Iw,Aw,In,In,Ip,Iw,Ip,Aw" 1200
  test "790:Cw,Cn,Cn" 1000
  test "1220:In,In,An,An,In,Iw,Iw,In,In,Ip,In,An,Iw" 4590
  test "570:Cw,Cn,Cp" 440
  test "310:Cn,Cw,An,An,Iw,Cp,Cw,Cn,Iw" 1100
  test "910:Aw,In,Iw,Iw,Iw,Iw,Iw,An,Cw,In" 2290
  test "460:Iw,Cw,Cw,Cn" 590
  test "240:Iw,Iw,In,Iw,In,In,Cn,In,An" 780
  test "1240:In,In,In,Ap,In,Cw,Iw,Iw,Iw,Aw,Cw" 2170
  test "1000:Iw,Ip,In,An,In,In,In,An,In,Iw,In,In,Iw,In,Iw,Iw,Iw,An" 5500
  test "180:In,Aw,Ip,Iw,In,Aw,In,Iw,Iw,In" 330
  test "440:In,Ip,Cp,Aw,Iw,In,An" 660
  test "1270:Ap,In,An,Ip,In,Ip,Ip" 1270
  0
```