type InputParser(str: string) = static member Parse (str:string) = let inputs = List.ofArray (str.Split(' ')) |> List.map (fun i -> match System.Int32.TryParse(i) with | (true, int) -> int | (false, int) -> invalidArg "inputs" "Input was not an integer.") let ticks = inputs.Head let movements = inputs.Tail ticks, movements type ICombinationLock = interface abstract member Input: string abstract member Clicks: int end type CombinationLock1(str: string) = let ticks, movements = InputParser.Parse(str) let Clockwise dst (acc, src) = if src > dst then (acc + (ticks + dst - src), dst) elif src = dst then (acc + ticks, dst) elif dst > src then (acc + (dst - src), dst) else (acc, dst) let CounterClockwise dst (acc, src) = if src > dst then (acc + (src - dst), dst) elif src = dst then (acc + ticks, dst) elif dst > src then (acc + (ticks + src - dst), dst) else (acc, dst) let Spin (acc, src: int) = (acc + ticks, src) let SpinClockwise (acc: int * int) (elem: int * int) = if snd acc <> snd elem then Spin acc else acc |> Clockwise (snd elem) let SpinCounterClockwise (acc: int * int) (elem: int * int) = if snd acc <> snd elem then Spin acc else acc |> CounterClockwise (snd elem) interface ICombinationLock with member this.Input = str member this.Clicks = let ApplyMovementIndex index elem = (index, elem) let HasEvenIndex elem = (fst elem % 2 = 0) let indexedMovements = Seq.mapi ApplyMovementIndex ([0] @ movements) let TurnDialAccumulateClicks acc elem = match elem with | (index, _) when index = 0 -> Clockwise (snd elem) acc | (index, _) when index = List.length movements && HasEvenIndex elem -> CounterClockwise (snd elem) acc | (index, _) when index = List.length movements -> Clockwise (snd elem) acc | _ when HasEvenIndex elem -> SpinCounterClockwise acc elem | _ -> SpinClockwise acc elem (* The first movement is executed as goto 0 from 0, or a full spin. Before any intermediate movements are made, a preliminary full spin is executed. The movement direction is determined by whether or not the movement index is even or odd. i.e. The 0th movement is even and counter-clockwise, the 1st movement is odd and clockwise. The final movement is executed as a direct spin to the final destination without a preliminary full spin. The movement direction is determined by whether or not the movement index is even or odd. *) let clicks = fst (Seq.fold TurnDialAccumulateClicks (0,0) indexedMovements) clicks type CombinationLock2(str: string) = let ticks, movements = InputParser.Parse(str) let f n x y z = let g a b = (n + b - a - 1) % n + 1 // (n + b - a - 1) gets the remainder, like Haskell's mod function appears to do let clicks = 3 * n + x + g y x + g y z if x = 0 then clicks - n else clicks // special handling to remove extra turn when the first movement is 0 interface ICombinationLock with member this.Input = str member this.Clicks = f ticks movements.[0] movements.[1] movements.[2] type CombinationLock3(str: string) = let ticks, movements = InputParser.Parse(str) let Move src dst = (ticks + dst - src - 1) % ticks + 1 // g a b = (n + b - a - 1) % n + 1 let Spin = Move 0 0 // 3 * n, as above in CombinationLock2, if executed 3 times will return 3 * ticks let Clockwise src dst = Move src dst // g y z, as above in CombinationLock2, a clockwise movement let CounterClockwise src dst = Move dst src // g y x, as above in CombinationLock2, a counter-clockwise movement // if ticks was set to 5 then we would find that 21 = spin + spin + cw 0 1 + spin + ccw 1 2 + cw 2 3 let SpinClockwise src dst = if src <> dst then Spin + Clockwise src dst else Clockwise src dst let SpinCounterClockwise src dst = if src <> dst then Spin + CounterClockwise src dst else CounterClockwise src dst interface ICombinationLock with member this.Input = str member this.Clicks = let ApplyMovementIndex index elem = (index, elem) let HasEvenIndex elem = (fst elem % 2 = 0) let indexedMovements = List.mapi ApplyMovementIndex ([0] @ movements) let TurnDialAccumulateClicks acc elem = let src = if fst elem > 0 then snd (indexedMovements.[fst elem - 1]) else 0 let dst = snd elem let clicks = match elem with | (index, _) when index = 0 -> Clockwise src dst | (index, _) when index = List.length movements && HasEvenIndex elem -> CounterClockwise src dst | (index, _) when index = List.length movements -> Clockwise src dst | _ when HasEvenIndex elem -> SpinCounterClockwise src dst | _ -> SpinClockwise src dst acc + clicks let clicks = List.fold TurnDialAccumulateClicks 0 indexedMovements clicks [] let main argv = let PrintLockInfo (lock: ICombinationLock) lockType = printfn "Number of clicks for input \"%s\" using %s was %d" lock.Input lockType lock.Clicks |> ignore let PrintSuite input = PrintLockInfo (new CombinationLock1(input)) "CombinationLock1" PrintLockInfo (new CombinationLock2(input)) "CombinationLock2" PrintLockInfo (new CombinationLock3(input)) "CombinationLock3" printfn "" PrintSuite "5 1 2 3" PrintSuite "5 0 0 0" PrintSuite "5 1 2 1" PrintSuite "5 3 2 1" PrintSuite "100 25 23 1" PrintLockInfo (new CombinationLock3("60 12 32 48 1 25 39 14 58")) "CombinationLock3" 0 // return an integer exit code