I was exploring Jupyter notebooks , that combines live code, markdown and data, through Microsoft's implementation, known as MS Azure Notebooks , putting together a small library of R and F# notebooks . As Microsoft's FAQ for the service describes it as : ...a multi-lingual REPL on steroids. This is a free service that provides Jupyter notebooks along with supporting packages for R, Python and F# as a service. This means you can just login and get going since no installation/setup is necessary. Typical usage includes schools/instruction, giving webinars, learning languages, sharing ideas, etc. Feel free to clone and comment... In R Azure Workbook for R - Memoisation and Vectorization Charting Correlation Matrices in R In F# Charnownes Constant in FSharp.ipynb Project Euler - Problems 18 and 67 - FSharp using Dynamic Programming
Problem
If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?
NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage.
Note
I have done two (2) solutions for this problem. The original in a prior post requires less code, but is 'ugly' and is less reusable that this match-based solution. This solution is based on smaller methods using the match function, with larger number functions able to call and reuse code from the functions for smaller numbers. Calling the method is much cleaner as well.
Solution (Match-Based)
let GetThousandthsLetters prefix fourth third second first =
match fourth with
| 0 -> " " + GetHundredthsLetters prefix third second first
| 1 -> "one thousand " + GetHundredthsLetters prefix third second first
| 2 -> "two thousand " + GetHundredthsLetters prefix third second first
| 3 -> "three thousand " + GetHundredthsLetters prefix third second first
| 4 -> "four thousand " + GetHundredthsLetters prefix third second first
| 5 -> "five thousand " + GetHundredthsLetters prefix third second first
| 6 -> "six thousand " + GetHundredthsLetters prefix third second first
| 7 -> "seven thousand " + GetHundredthsLetters prefix third second first
| 8 -> "eight thousand " + GetHundredthsLetters prefix third second first
| 9 -> "nine thousand " + GetHundredthsLetters prefix third second first
let GetHundredthsLetters prefix third second first =
match third with
| 0 -> match second with
| 0 -> ""
| _ -> prefix + GetTenthsLetters second first
| 1 -> match second with
| 0 -> match first with
| 0 -> "one hundred"
| _ -> "one hundred" + prefix + GetFirstDigitLetters first
| _ -> "one hundred" + prefix + GetTenthsLetters second first
| 2 -> match second with
| 0 -> match first with
| 0 -> "two hundred"
| _ -> "two hundred" + prefix + GetFirstDigitLetters first
| _ -> "two hundred" + prefix + GetTenthsLetters second first
| 3 -> match second with
| 0 -> match first with
| 0 -> "three hundred"
| _ -> "three hundred" + prefix + GetFirstDigitLetters first
| _ -> "three hundred" + prefix + GetTenthsLetters second first
| 4 -> match second with
| 0 -> match first with
| 0 -> "four hundred"
| _ -> "four hundred" + prefix + GetFirstDigitLetters first
| _ -> "four hundred" + prefix + GetTenthsLetters second first
| 5 -> match second with
| 0 -> match first with
| 0 -> "five hundred"
| _ -> "five hundred" + prefix + GetFirstDigitLetters first
| _ -> "five hundred" + prefix + GetTenthsLetters second first
| 6 -> match second with
| 0 -> match first with
| 0 -> "six hundred"
| _ -> "six hundred" + prefix + GetFirstDigitLetters first
| _ -> "six hundred" + prefix + GetTenthsLetters second first
| 7 -> match second with
| 0 -> match first with
| 0 -> "seven hundred"
| _ -> "seven hundred" + prefix + GetFirstDigitLetters first
| _ -> "seven hundred" + prefix + GetTenthsLetters second first
| 8 -> match second with
| 0 -> match first with
| 0 -> "eight hundred"
| _ -> "eight hundred" + prefix + GetFirstDigitLetters first
| _ -> "eight hundred" + prefix + GetTenthsLetters second first
| 9 -> match second with
| 0 -> match first with
| 0 -> "nine hundred"
| _ -> "nine hundred" + prefix + GetFirstDigitLetters first
| _ -> "nine hundred" + prefix + GetTenthsLetters second first
let GetTenthsLetters second first =
match second with
| 0 -> match first with
| 0 -> ""
| _ -> GetFirstDigitLetters first
| 1 -> match first with
| 0 -> "ten"
| 1 -> "eleven"
| 2 -> "twelve"
| 3 -> "thirteen"
| 4 -> "fourteen"
| 5 -> "fifteen"
| 6 -> "sixteen"
| 7 -> "seventeen"
| 8 -> "eighteen"
| 9 -> "nineteen"
| 2 -> match first with
| 0 -> "twenty"
| _ -> "twenty" + "-" + GetFirstDigitLetters first
| 3 -> match first with
| 0 -> "thirty"
| _ -> "thirty" + "-" + GetFirstDigitLetters first
| 4 -> match first with
| 0 -> "forty"
| _ -> "forty" + "-" + GetFirstDigitLetters first
| 5 -> match first with
| 0 -> "fifty"
| _ -> "fifty" + "-" + GetFirstDigitLetters first
| 6 -> match first with
| 0 -> "sixty"
| _ -> "sixty" + "-" + GetFirstDigitLetters first
| 7 -> match first with
| 0 -> "seventy"
| _ -> "seventy" + "-" + GetFirstDigitLetters first
| 8 -> match first with
| 0 -> "eighty"
| _ -> "eighty" + "-" + GetFirstDigitLetters first
| 9 -> match first with
| 0 -> "ninety"
| _ -> "ninety" + "-" + GetFirstDigitLetters first
let GetFirstDigitLetters num =
match num with
| 0 -> ""
| 1 -> "one"
| 2 -> "two"
| 3 -> "three"
| 4 -> "four"
| 5 -> "five"
| 6 -> "six"
| 7 -> "seven"
| 8 -> "eight"
| 9 -> "nine"
let ConvertNumberToString num =
let numAsString = num.ToString()
let numCharArray = numAsString.ToCharArray()
let numIntArray = numCharArray |> Array.map (fun x -> System.Int32.Parse(x.ToString()))
let ln = numIntArray.Length
if ln = 4 then
let result = GetThousandthsLetters " and " numIntArray.[0] numIntArray.[1] numIntArray.[2] numIntArray.[3]
result
elif ln = 3 then
let result = GetHundredthsLetters " and " numIntArray.[0] numIntArray.[1] numIntArray.[2]
result
elif ln = 2 then
let result = GetTenthsLetters numIntArray.[0] numIntArray.[1]
result
else
let result = GetFirstDigitLetters numIntArray.[0]
result
let testNumber =
let arrWords = [1..1000] |> List.map (fun x -> ConvertNumberToString x)
let arrWordsMinusSpaces = arrWords |> List.map (fun x -> x.Replace(" ", ""))
let arrWordsMinusHyphen = arrWordsMinusSpaces |> List.map (fun x -> x.Replace("-", ""))
let arrLengths = arrWordsMinusHyphen |> List.map (fun x-> x.Length)
List.sum arrLengths
If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?
NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage.
Note
I have done two (2) solutions for this problem. The original in a prior post requires less code, but is 'ugly' and is less reusable that this match-based solution. This solution is based on smaller methods using the match function, with larger number functions able to call and reuse code from the functions for smaller numbers. Calling the method is much cleaner as well.
Solution (Match-Based)
let GetThousandthsLetters prefix fourth third second first =
match fourth with
| 0 -> " " + GetHundredthsLetters prefix third second first
| 1 -> "one thousand " + GetHundredthsLetters prefix third second first
| 2 -> "two thousand " + GetHundredthsLetters prefix third second first
| 3 -> "three thousand " + GetHundredthsLetters prefix third second first
| 4 -> "four thousand " + GetHundredthsLetters prefix third second first
| 5 -> "five thousand " + GetHundredthsLetters prefix third second first
| 6 -> "six thousand " + GetHundredthsLetters prefix third second first
| 7 -> "seven thousand " + GetHundredthsLetters prefix third second first
| 8 -> "eight thousand " + GetHundredthsLetters prefix third second first
| 9 -> "nine thousand " + GetHundredthsLetters prefix third second first
let GetHundredthsLetters prefix third second first =
match third with
| 0 -> match second with
| 0 -> ""
| _ -> prefix + GetTenthsLetters second first
| 1 -> match second with
| 0 -> match first with
| 0 -> "one hundred"
| _ -> "one hundred" + prefix + GetFirstDigitLetters first
| _ -> "one hundred" + prefix + GetTenthsLetters second first
| 2 -> match second with
| 0 -> match first with
| 0 -> "two hundred"
| _ -> "two hundred" + prefix + GetFirstDigitLetters first
| _ -> "two hundred" + prefix + GetTenthsLetters second first
| 3 -> match second with
| 0 -> match first with
| 0 -> "three hundred"
| _ -> "three hundred" + prefix + GetFirstDigitLetters first
| _ -> "three hundred" + prefix + GetTenthsLetters second first
| 4 -> match second with
| 0 -> match first with
| 0 -> "four hundred"
| _ -> "four hundred" + prefix + GetFirstDigitLetters first
| _ -> "four hundred" + prefix + GetTenthsLetters second first
| 5 -> match second with
| 0 -> match first with
| 0 -> "five hundred"
| _ -> "five hundred" + prefix + GetFirstDigitLetters first
| _ -> "five hundred" + prefix + GetTenthsLetters second first
| 6 -> match second with
| 0 -> match first with
| 0 -> "six hundred"
| _ -> "six hundred" + prefix + GetFirstDigitLetters first
| _ -> "six hundred" + prefix + GetTenthsLetters second first
| 7 -> match second with
| 0 -> match first with
| 0 -> "seven hundred"
| _ -> "seven hundred" + prefix + GetFirstDigitLetters first
| _ -> "seven hundred" + prefix + GetTenthsLetters second first
| 8 -> match second with
| 0 -> match first with
| 0 -> "eight hundred"
| _ -> "eight hundred" + prefix + GetFirstDigitLetters first
| _ -> "eight hundred" + prefix + GetTenthsLetters second first
| 9 -> match second with
| 0 -> match first with
| 0 -> "nine hundred"
| _ -> "nine hundred" + prefix + GetFirstDigitLetters first
| _ -> "nine hundred" + prefix + GetTenthsLetters second first
let GetTenthsLetters second first =
match second with
| 0 -> match first with
| 0 -> ""
| _ -> GetFirstDigitLetters first
| 1 -> match first with
| 0 -> "ten"
| 1 -> "eleven"
| 2 -> "twelve"
| 3 -> "thirteen"
| 4 -> "fourteen"
| 5 -> "fifteen"
| 6 -> "sixteen"
| 7 -> "seventeen"
| 8 -> "eighteen"
| 9 -> "nineteen"
| 2 -> match first with
| 0 -> "twenty"
| _ -> "twenty" + "-" + GetFirstDigitLetters first
| 3 -> match first with
| 0 -> "thirty"
| _ -> "thirty" + "-" + GetFirstDigitLetters first
| 4 -> match first with
| 0 -> "forty"
| _ -> "forty" + "-" + GetFirstDigitLetters first
| 5 -> match first with
| 0 -> "fifty"
| _ -> "fifty" + "-" + GetFirstDigitLetters first
| 6 -> match first with
| 0 -> "sixty"
| _ -> "sixty" + "-" + GetFirstDigitLetters first
| 7 -> match first with
| 0 -> "seventy"
| _ -> "seventy" + "-" + GetFirstDigitLetters first
| 8 -> match first with
| 0 -> "eighty"
| _ -> "eighty" + "-" + GetFirstDigitLetters first
| 9 -> match first with
| 0 -> "ninety"
| _ -> "ninety" + "-" + GetFirstDigitLetters first
let GetFirstDigitLetters num =
match num with
| 0 -> ""
| 1 -> "one"
| 2 -> "two"
| 3 -> "three"
| 4 -> "four"
| 5 -> "five"
| 6 -> "six"
| 7 -> "seven"
| 8 -> "eight"
| 9 -> "nine"
let ConvertNumberToString num =
let numAsString = num.ToString()
let numCharArray = numAsString.ToCharArray()
let numIntArray = numCharArray |> Array.map (fun x -> System.Int32.Parse(x.ToString()))
let ln = numIntArray.Length
if ln = 4 then
let result = GetThousandthsLetters " and " numIntArray.[0] numIntArray.[1] numIntArray.[2] numIntArray.[3]
result
elif ln = 3 then
let result = GetHundredthsLetters " and " numIntArray.[0] numIntArray.[1] numIntArray.[2]
result
elif ln = 2 then
let result = GetTenthsLetters numIntArray.[0] numIntArray.[1]
result
else
let result = GetFirstDigitLetters numIntArray.[0]
result
let testNumber =
let arrWords = [1..1000] |> List.map (fun x -> ConvertNumberToString x)
let arrWordsMinusSpaces = arrWords |> List.map (fun x -> x.Replace(" ", ""))
let arrWordsMinusHyphen = arrWordsMinusSpaces |> List.map (fun x -> x.Replace("-", ""))
let arrLengths = arrWordsMinusHyphen |> List.map (fun x-> x.Length)
List.sum arrLengths
This comment has been removed by a blog administrator.
ReplyDeleteI think you are right, in that the code could be improved, but I was primarily focused on removing the quirky logic used in the prior GetNumber function, as well as exploring the use of match. The code is verbose, maybe unnecessarily so, and I am sure there are better implementations.
ReplyDeleteThat said, I think it would be useful for my self-development to search the internet for 'better, best' solutions after I find my solutions, and post links to the best solutions as addenda.
From an anonymous poster, which I accidentally deleted, and is the reason for my response above:
ReplyDeleteI still don't like the second solution much in the sense that I believe that your convertnumbertostring isn't really idiomatic functional code. Basically with higher order fucntions and recursions you can convert any number from 100 to 999 with a single line of code, and with another one, a conversion for every number from 1000 to 999,999. For the cases between 20 and 100 you'll need a little more work because of some spelling things (forty != four + ty, but for some of the multiples of 10 this does hold) but even there you can apply some recursion.