Out of pure curiosity, I decided to create a short F# script to calculate the letter frequencies based on the input file. Everything works smoothly and as I would expect. My questions regarding my code are as follows:
Does it conform to formal F# standards?
I have done my best to keep it as functional as possible while maintaining a style similar to the most common one I've seen in example programs, but I am unsure if it is up to par.
Does it maximize F#'s capabilities while maintaining readability?
This is as it seems, I am curious as to whether or not I have used all relevant and applicable F# functions inside the program (e.g. map). The portion I am most concerned about is this bit:
Array.map (fun z -> let e, f = z match count |> Array.tryFind (fun a -> let f,s = a (e = f)) with | Some x -> (e, Convert.ToInt64(snd x) + f) | None -> z)
I am unsure if my approach is proper, that
Array.tryFindbit is especially horrendous, but I can't think of a better way to approach this. In addition, instead of the
tupletype, should I instead go for
recordsor is there some other, more appropriate, solution that I am missing?
Are there any ways to optimize or simplify the program I have written?
I have a decently powerful machine (i5 6600, 16GB RAM) and per FSI's
#timedirective, the program has the following footprint when processing ~10.1gb of text
Real: 00:14:30.479, CPU: 00:14:21.421, GC gen0: 208720, gen1: 96, gen2: 8. I have had very little optimization experience whatsoever, so I am unsure if (by my calculations) 11.89mb/s is a good speed for such a relatively simple program.
#if INTERACTIVE #time #else module LetterFreqCalc #endif open System open System.IO let explode (x:string) = [|for c in x -> c|] let ToUpper (x:string) = x.ToUpper() let numLetterInWord word = word |> ToUpper |> explode |> Array.countBy id let sumTupArray (tupArr:(char*int64)) = tupArr |> Array.sumBy snd let div (x:float) (y:float) = y/x let mul (x:float) (y:float) = x*y let rec CalcNextLine (reader:StreamReader) accumulator = match reader.EndOfStream with | true -> accumulator | false -> let line = reader.ReadLine() let count = numLetterInWord line accumulator |> Array.map (fun z -> let e, f = z match count |> Array.tryFind (fun a -> let f,s = a (e = f)) with | Some x -> (e, Convert.ToInt64(snd x) + f) | None -> z) |> CalcNextLine reader let CalcLetterFreqOfFile (path:string) = let reader = new StreamReader(path) let start = [|for c in [|'A'..'Z'|] -> (c,0L)|] CalcNextLine reader start [<EntryPoint>] let main argv = let occurences = @"dictionary.txt" |> CalcLetterFreqOfFile let total = occurences |> Array.sumBy snd |> float let freqs = occurences |> Array.map (fun x -> (fst x, (let percentage = snd x |> float |> div total |> mul 100.0 Math.Round(percentage, 2)))) printfn "%A" freqs printfn "%A" (freqs |> Array.sortByDescending snd) printfn "%A" argv 0 // return an integer exit code