I wrote a part of the program that would calculate the difference between two dates, but I can't add the control of leap years ( ((year % 4 = 0) && ((year % 100 <> 0) || (year % 400 = 0))) ). For example: if my input is 01 01 2001 and 01 01 2005 I get in output 1460, but the year 2004 is a leap year so my output should be 1461.
This is my program:
let rec month_length (month : int) (year : int) : int =
match month with
| 0 -> failwith "Wrong month"
| _ when month > 12 -> failwith "Wrong month'"
| 1 -> 31
| 2 -> month_length (month - 1) year + 28
| 4 | 6 | 9 | 11 -> 30 + month_length (month - 1) year
| n -> 31 + month_length (n - 1) year
let data_to_day (day : int) (month : int) (year : int) : int =
if month = 1 then day
else day + month_length (month - 1) year
let data_difference (day1 : int) (month1 : int) (year1 : int) (day2 : int) (month2 : int) (year2 : int) : int =
let dy = year1 - year2
data_to_day day1 month1 year1 - data_to_day day2 month2 year2 + (dy * 365)
Does anyone have any ideas?
Thanks in advance!
As suggested by Functional_S in above comment, code is not compensating leap year days.
With assumption of not using any libraries below is modified code. Its not elegant solution but it is one way to handle leap year / month days.
let feb_month_days year =
if ((year % 4 = 0) && ((year % 100 <> 0) || (year % 400 = 0))) then 29
else 28
let year_days year =
if ((year % 4 = 0) && ((year % 100 <> 0) || (year % 400 = 0))) then 366
else 365
let rec month_length (month : int) (year : int) : int =
match month with
| 0 -> failwith "Wrong month"
| _ when month > 12 -> failwith "Wrong month'"
| 1 -> 31
| 2 -> month_length (month - 1) year + (feb_month_days year)
| 4 | 6 | 9 | 11 -> 30 + month_length (month - 1) year
| n -> 31 + month_length (n - 1) year
let data_to_day (day : int) (month : int) (year : int) : int =
if month = 1 then
day
else
day + month_length (month - 1) year
let data_difference (day1 : int) (month1 : int) (year1 : int) (day2 : int) (month2 : int) (year2 : int) : int =
let dy = year1 - year2
let range_year_1 = year1 - 1
let range_year_2 = year2 + 1
let year2_days = (year_days year2) - (data_to_day day2 month2 year2)
let year1_days = data_to_day day1 month1 year1
let between_year_days = seq {for y in range_year_2..range_year_1 do yield year_days y} |> Seq.sum
year2_days + year1_days + between_year_days
printfn "%A" (data_difference 01 01 2005 01 01 2001) // gives back 1461
Its almost same as above code with addition on 'feb_month_days' and 'year_days' functions
Related
I have this data frame
AutoStat_1 AutoStat_2 Mode_1 Mode_2 Setpoint_1 Setpoint_2
0 -> 0 0 1 1 23 24
1 -> 0 1 1 0 23 27
2 -> 1 1 3 0 26 27
3 -> 1 0 3 1 26 24
4 -> 0 0 1 2 24 24
5 -> 0 0 1 2 24 24
6 -> 2 3 0 4 24 26
7 -> 2 3 0 4 25 26
The requirement is that if AutoStat_i is not 0 then Mode_i and Setpoint_i will be the value of the above (in-front) of which AutoStat_i is 0
The result should be (notice the column Setpoint_i and Mode_i are different than above)
AutoStat_1 AutoStat_2 Mode_1 Mode_2 Setpoint_1 Setpoint_2
0 -> 0 0 1 1 23 24
1 -> 0 1 1 1 23 24
2 -> 1 1 1 1 23 24
3 -> 1 0 1 1 23 24
4 -> 0 0 1 2 24 24
5 -> 0 0 1 2 24 24
6 -> 2 3 1 2 24 24
7 -> 2 3 1 2 24 24
What've I tried:
My idea is for each set i of (AutoStat_i, Mode_i, Setpoint_i), scan each row if AutoStat_i is <> 0 then set the other values to NaN, after that I will just do the fillMissing with Direction.Forward. Below is the impementation
let calculateNonSFi (df:Frame<_,string>) idx =
let autoStatusName = sprintf "AutoStat_%d" idx
let setpointName = sprintf "Setpoint_%d" idx
let modeName = sprintf "Mode_%d" idx
let setMissingOnMode (s:ObjectSeries<string>) =
let s2 = s.As<float>()
if s2.[autoStatusName] <> 0. then
Series.replaceArray [|setpointName;modeName|] Double.NaN s2
else
s2
df.Rows
|> Series.mapValues setMissingOnMode
|> Frame.ofRows
|> Frame.fillMissing Direction.Forward
|> Frame.fillMissing Direction.Backward
// for each set i do the folding
[0..150]
|> List.fold calculateNonSFi df
It gave me the expected results, however, for 150sets of 8000rows, it took more than 30 minutes to complete. I kinda see where it's wrong as for every set it acts on the whole dataset but I cannot think of a better way.
The logic is quite simple. I believe there should be a better way, do advice, thanks.
UPDATE
Here is the code for reproduction
open Deedle
open System
let df =
[
{| AutoStat_1=0;Setpoint_1=23;Mode_1=1;AutoStat_2=0;Setpoint_2=24;Mode_2=1|}
{| AutoStat_1=0;Setpoint_1=23;Mode_1=1;AutoStat_2=1;Setpoint_2=24;Mode_2=1|}
{| AutoStat_1=1;Setpoint_1=23;Mode_1=1;AutoStat_2=1;Setpoint_2=24;Mode_2=1|}
{| AutoStat_1=1;Setpoint_1=23;Mode_1=1;AutoStat_2=0;Setpoint_2=24;Mode_2=1|}
{| AutoStat_1=0;Setpoint_1=24;Mode_1=1;AutoStat_2=0;Setpoint_2=24;Mode_2=2|}
{| AutoStat_1=0;Setpoint_1=24;Mode_1=1;AutoStat_2=0;Setpoint_2=24;Mode_2=2|}
{| AutoStat_1=2;Setpoint_1=24;Mode_1=1;AutoStat_2=3;Setpoint_2=24;Mode_2=2|}
{| AutoStat_1=2;Setpoint_1=24;Mode_1=1;AutoStat_2=3;Setpoint_2=24;Mode_2=2|}
] |> Frame.ofRecords
df.Print()
let calculateNonSFi (df:Frame<_,string>) idx =
let autoStatusName = sprintf "AutoStat_%d" idx
let setpointName = sprintf "Setpoint_%d" idx
let modeName = sprintf "Mode_%d" idx
let setMissingOnMode (s:ObjectSeries<string>) =
let s2 = s.As<float>()
if s2.[autoStatusName] <> 0. then
Series.replaceArray [|setpointName;modeName|] Double.NaN s2
else
s2
df.Rows
|> Series.mapValues setMissingOnMode
|> Frame.ofRows
|> Frame.fillMissing Direction.Forward
let df1 =
[1..2]
|> List.fold calculateNonSFi df
df1.Print()
Advice/Answer from Tomas
df
|> Frame.mapRows (fun _ o ->
[ for i in 0 .. 150 do
let au = o.GetAs<float>("AutoStat_" + string i)
yield "AutoStat_" + string i, au
yield "Mode_" + string i, if au <> 0. then nan else o.GetAs("Mode_" + string i)
yield "Setpoint_" + string i, if au <> 0. then nan else o.GetAs("Setpoint_" + string i) ]
|> series )
|> Frame.ofRows
|> Frame.fillMissing Direction.Forward
which yields correct result but in different column order hence my mistake in the earlier edit
AutoStat_1 Mode_1 Setpoint_1 AutoStat_2 Mode_2 Setpoint_2
0 -> 0 1 23 0 1 24
1 -> 0 1 23 1 1 24
2 -> 1 1 23 1 1 24
3 -> 1 1 23 0 1 24
4 -> 0 1 24 0 2 24
5 -> 0 1 24 0 2 24
6 -> 2 1 24 3 2 24
7 -> 2 1 24 3 2 24
First of all, I think your strategy of setting Mode_i and Setpoint_i to NA when AutoStat_i is not 0 and then filling the missing values is a nice approach.
You can certainly make it a bit faster by moving the fillMissing call outside of the calculateNonSFi function - the fillMissing operation will run on the whole frame, so you need to run this once at the end.
The second thing would be to find a way of setting the NA values that only iterates over the frame once. One option (I have not tested this) would be to use Frame.mapRows and, inside the function, iterate over all the columns (rather than iterating over all the columns and calling mapRows repeatedly). Something like:
df
|> Frame.mapRows (fun _ o ->
[ for i in 0 .. 150 do
let au = o.GetAs<float>("AutoStat_" + string i)
yield "AutoStat_" + string i, au
yield "Mode_" + string i, if au = 0. then nan else o.GetAs("Mode_" + string i)
yield "Setpoint_" + string i, if au = 0. then nan else o.GetAs("Setpoint_" + string i) ]
|> series )
|> Frame.ofRows
I'm parsing HTML (via HAP) and am now parsing specific table column content per row (a collection of TD elements)
Note: Not using FSharp.Data's HTML parser as it is broken with html that contains <Script> code that cause the CSS Selectors to fail (known issue)
The type that I am trying to map a row of data into (10 "columns" of varying types) :
type DailyRow = { C0: string; C1: string; C2: int; C3: decimal; C4: string; C5: string; C6: int; C7: decimal; C8: decimal; C9: int }
My ugly but working function that maps column positions into the record field (yes, anything that does not parse correctly should explode):
let dailyRow = fun (record:DailyRow, column:int, node:HtmlNode) ->
printfn "dailyRow: Column %i has value %s" column node.InnerText
match column with
| 0 -> {record with C0 = node.InnerText }
| 1 -> {record with C1 = node.InnerText }
| 2 -> {record with C2 = (node.InnerText |> int) }
| 3 -> {record with C3 = Decimal.Parse(node.InnerText, NumberStyles.Currency) }
| 4 -> {record with C4 = node.InnerText }
| 5 -> {record with C5 = node.InnerText }
| 6 -> {record with C6 = Int32.Parse(node.InnerText, NumberStyles.AllowThousands) }
| 7 -> {record with C7 = Decimal.Parse(node.InnerText, NumberStyles.Currency) }
| 8 -> {record with C8 = Decimal.Parse(node.InnerText, NumberStyles.Currency) }
| 9 -> {record with C9 = (node.InnerText |> int) }
| _ -> raise (System.MissingFieldException("New Field in Chart Data Found: " + column.ToString()))
Some test code:
let chartRow = { C0 = ""; C1 = ""; C2 = 0; C3 = 0.0M; C4 = "" ; C5 = ""; C6 = 0; C7 = 0.0M; C8 = 0.0M; C9 = 0 }
let columnsToParse = row.SelectNodes "td" // 1 row of 10 columns
let x = columnsToParse
|> Seq.mapi (fun i x -> dailyRow(chartRow, i, x))
The issue, since I am passing in a immutable record and receiving a new record from the dailyRow function via Seq.mapi (using the index to map to the column number), I will end up with 10 records, each with one of their values property set.
In C# I would just pass dailyRow a ref'd object and update it in place, what would be the F# idiomatic way of handling this?
Simplest option, if you don't mind an array allocation:
let nodes = seq [...]
let arr = nodes |> Seq.map (fun n -> n.InnerText) |> Array.ofSeq
let record =
{ C0 = arr.[0]
C1 = arr.[1]
C2 = int arr.[2]
C3 = Decimal.Parse(arr.[3], NumberStyles.Currency)
C4 = arr.[4]
C5 = arr.[5]
C6 = Int32.Parse(arr.[6], NumberStyles.AllowThousands)
C7 = Decimal.Parse(arr.[7], NumberStyles.Currency)
C8 = Decimal.Parse(arr.[8], NumberStyles.Currency)
C9 = int arr.[9] }
I cannot understand why resmplU in the code below is an empty series.
I suspect the problem is I do not understand the role of the parameter nextKey in the signature of resampleUniform. The help says it "is used to generate all keys in the range," but I could not figure out what that means.
open Deedle
let dateRange skip (startDate: System.DateTime) endDate =
Seq.initInfinite float
|> Seq.map (fun i -> startDate.AddDays (skip * i))
|> Seq.takeWhile (fun dt -> dt <= endDate)
let dt3 = DateTime(2017,4,1)
let dt4 = DateTime(2017,4,30)
let dt5 = DateTime(2017,4,15)
let dateR = dateRange 2.0 dt3 dt4
let valsR = [1..dateR |> Seq.length]
let tseries = Series(dateR, valsR)
let resmplU =
tseries
|> Series.resampleUniform Lookup.ExactOrGreater (fun x -> x < dt5) id
After running this code I get:
val dt3 : DateTime = 4/1/2017 12:00:00 AM
val dt4 : DateTime = 4/30/2017 12:00:00 AM
val dt5 : DateTime = 4/15/2017 12:00:00 AM
val dateR : seq<DateTime>
val valsR : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15]
val tseries : Series<DateTime,int> =
series [ 4/1/2017 12:00:00 AM => 1; 4/3/2017 12:00:00 AM => 2; 4/5/2017 12:00:00 AM => 3; 4/7/2017 12:00:00 AM => 4; 4/9/2017 12:00:00 AM => 5; ... ; 4/29/2017 12:00:00 AM => 15]
val resmplU : Series<bool,Series<DateTime,int>> = series [ ]
Any insights?
The Series.resampleUniform function lets you rescale values in a series to a new set of keys. For example, given your series:
01/04/2017 -> 1
03/04/2017 -> 2
05/04/2017 -> 3
07/04/2017 -> 4
09/04/2017 -> 5
11/04/2017 -> 6
13/04/2017 -> 7
15/04/2017 -> 8
17/04/2017 -> 9
19/04/2017 -> 10
21/04/2017 -> 11
23/04/2017 -> 12
25/04/2017 -> 13
27/04/2017 -> 14
29/04/2017 -> 15
You can resample the series so that the keys are days in the month from 1 to 29. For each key (day), you will get values on that specific day, or values on the next day for which they are available:
tseries
|> Series.resampleUniform Lookup.ExactOrGreater (fun dt -> dt.Day) (fun d -> d + 1)
The first function dt -> dt.Day turns keys from the original series into keys in the new series (to illustrate how this works, I'm using int as keys for the returned series) and the second function d -> d + 1 calculates the next key of the target series.
EDIT I think the problem with your idea of using bool as the key is that the resampling function needs to calculate one more key after the two keys in the series you want to get - so that it can make sure it reached the end. This does not work for bool as there are only two values. The following works though:
tseries
|> Series.resampleUniform Lookup.ExactOrGreater
(fun x -> if x < dt5 then 0 else 1) (fun n -> n + 1)
I wrote this function which computes the palindrome of a number :
let palindrome n =
let mutable current = n
let mutable result = 0
while(current > 0) do
result <- result * 10 + current % 10
current <- current / 10
result
How can I rewrite it in a more functional way ?
It's not quite clear what you want to do. The palindrome function as given simply reverses the digits of an integer:
> palindrome 1;;
val it : int = 1
> palindrome 12;;
val it : int = 21
> palindrome 123;;
val it : int = 321
> palindrome 9852;;
val it : int = 2589
Those aren't palindromic numbers, but let's split the problem into smaller building blocks.
You can easily split an integer into a list of digits. In fact, splitting into a reverse list of digits is the easiest way I could think of:
let rec revdigits i =
let tens = i / 10
if tens = 0
then [i]
else
let ones = i % 10
ones :: revdigits tens
This is a function of the type int -> int list.
Examples:
> revdigits 1;;
val it : int list = [1]
> revdigits 12;;
val it : int list = [2; 1]
> revdigits 123;;
val it : int list = [3; 2; 1]
> revdigits 9852;;
val it : int list = [2; 5; 8; 9]
It's also easy to concatenate a list of digits into a number:
let rec concat digits =
match digits with
| [] -> 0
| h :: t -> h * int (10. ** float t.Length) + concat t
This function has the type int list -> int.
Examples:
> concat [1];;
val it : int = 1
> concat [1; 2];;
val it : int = 12
> concat [1; 2; 3];;
val it : int = 123
> concat [2; 5; 8; 9];;
val it : int = 2589
With these building blocks, you can easily compose a function that does the same as the palindrome function:
let reverse = revdigits >> concat
This function has the type int -> int.
Examples:
> reverse 1;;
val it : int = 1
> reverse 12;;
val it : int = 21
> reverse 123;;
val it : int = 321
> reverse 2589;;
val it : int = 9852
Bonus: if you don't want to reverse the digits, you can do it like this instead, but I don't think this version is tail recursive:
let rec digits i =
let tens = i / 10
if tens = 0
then [i]
else
let ones = i % 10
digits tens # [ones]
This function has the type int -> int list.
Examples:
> digits 1;;
val it : int list = [1]
> digits 12;;
val it : int list = [1; 2]
> digits 123;;
val it : int list = [1; 2; 3]
> digits 9852;;
val it : int list = [9; 8; 5; 2]
You can do it with a tail-recursive function. Match the value of result : if his value = 0 then return the result else do the computations on current and result.
let palindrome n =
let rec rec_palindrome current result = match current with
| 0 -> result
| _ -> rec_palindrome (result * 10 + current % 10) (current / 10)
rec_palindrome n 0
Plus, in my version, this is no mutable values.
I'm looking for the moral equivalent of ghci's:
Prelude> :t 1 + 2
1 + 2 :: Num a => a
In F# Interactive you get that after each execution:
> (+);;
val it : (int -> int -> int) = <fun:it#1>
> 1 + 2;;
val it : int = 3
> printfn "Hi";;
Hi
val it : unit = ()