Last active
December 16, 2015 15:39
-
-
Save robertpi/5457903 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ParkingMeterKata | |
open System | |
// coins accepted by the system | |
type Coin = | |
| TenCents = 10 | |
| TwentyCents = 20 | |
| FiftyCents = 50 | |
| OneEuro = 100 | |
| TwoEuro = 200 | |
// convert a coint into it's amount in Euro's | |
let amountOfCoin (coin: Coin) = | |
Convert.ToDecimal (coin |> int) / 100m | |
// calculate the total amount of a list of coins | |
let amountOfCoins (coins: seq<Coin>) = | |
coins |> Seq.map amountOfCoin |> Seq.sum | |
// defines how many minutes (second column) can be bought for an amount in euros (first column | |
// it is important that it's in decending order | |
let priceTable = | |
[ 8.M, 600M | |
5.M, 300M | |
3.M, 120M | |
2.M, 60M | |
1.M, 20M ] | |
// calculate how many minutes an amount in euros will buy you | |
let getMinutesBought (amount: Decimal) = | |
// find which price range this amount falls into | |
let priceRange = priceTable |> Seq.tryFind (fun (price, time) -> amount >= price) | |
match priceRange with | |
| Some (price, time) -> | |
// amount of time we can buy is the ratio of the amount we've given and the cost of range | |
// times the number of minutes in this price range | |
(amount / price) * time | |
| None -> failwith "Not enough" | |
// calculates wether this is a day where we need to pay | |
let isExcludeDay (date: DateTime) = | |
date.DayOfWeek = DayOfWeek.Sunday | |
|| date.Month = 8 | |
// TODO public holidates | |
// number of minutes in a day, a "day" is 10 hours long as we pay between 9h and 19h | |
let minutesInADay = 600M | |
// calculate what day we've paid up to if it's n days later | |
let getNthPayingDay n (startDate: DateTime) = | |
// creates an infinite sequence of the paying days starting at given date | |
let payingDays (startDate: DateTime) = | |
// create an infinite sequence of all dates from given date | |
let allDates = Seq.initInfinite (fun x -> startDate.AddDays( x |> float)) | |
// filter out any dates that aren't paying dates | |
allDates |> Seq.filter (fun x -> not (isExcludeDay x)) | |
// find the nth element in this list | |
Seq.nth n (payingDays startDate) | |
// test to see if given date is outside the paying range | |
let afterPayingTime (date: DateTime) = | |
date.Hour > 19 | |
// test to see if we're before the time where we need to pay | |
let beforePayingTime (date: DateTime) = | |
date.Hour < 9 | |
// Add the minutes we bought to current time, taking into account opening | |
// hours and excluded days | |
let addTime (startDate: DateTime) (minutes: Decimal) = | |
// first caculate how many days we need to add | |
let days = | |
(Math.Floor (minutes / minutesInADay)|> int) + | |
(if afterPayingTime startDate then 1 else 0) | |
// then calculate how many minutes during this day we need to add | |
let remainingMinutes = minutes % minutesInADay |> float | |
// add on the number of days | |
let date = if days > 0 then getNthPayingDay days startDate else startDate | |
// if we're before the time were we need to pay bring the date up to the paying time | |
let date = if beforePayingTime date then date.Date.AddHours(9.) else date | |
// add all the minutes we've bought for today (this can't be > minutesInADay) | |
let dateWithAllMinsAdded = date.AddMinutes(remainingMinutes) | |
// see if we've passed the hours we're we need to pay | |
if afterPayingTime dateWithAllMinsAdded then | |
// find the next paying day and add remaing minutes | |
let nextDay = getNthPayingDay 1 date | |
let nextDayAt9 = nextDay.Date.AddHours(9.) | |
let remainingMins = | |
dateWithAllMinsAdded - date.Date.AddHours(19.) | |
nextDayAt9.Add( remainingMins) | |
else | |
// otherwise just return this date | |
dateWithAllMinsAdded | |
// compose getMinutesBought and addTime to find the end time | |
let calcuateEndDateFromAmount (time: DateTime) (amount: Decimal) = | |
getMinutesBought amount | |
|> addTime time | |
// compose amountOfCoins and calcuateEndDateFromAmount to find what the end date | |
// for the coins we've used to pay with | |
let calcumateEndDateFromCoins (time: DateTime) (coins: seq<Coin>) = | |
coins |> amountOfCoins |> calcuateEndDateFromAmount time |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment