From 25aa38e538510b45e03e57fad7c1b2d99e9214b9 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Thu, 2 Jun 2022 22:05:22 +1200 Subject: [PATCH 01/13] Initial commit on optimisation branch --- src/en.jl | 89 ++++++------- src/en/ordinal_dictionaries.jl | 3 + src/en/utils.jl | 23 ++++ src/en_orig.jl | 223 +++++++++++++++++++++++++++++++++ test/Project.toml | 3 - test/runtests.jl | 3 +- 6 files changed, 286 insertions(+), 58 deletions(-) create mode 100644 src/en/utils.jl create mode 100644 src/en_orig.jl delete mode 100644 test/Project.toml diff --git a/src/en.jl b/src/en.jl index 153c466a..79a888e4 100644 --- a/src/en.jl +++ b/src/en.jl @@ -1,32 +1,30 @@ +# dictionaries include(joinpath(@__DIR__, "en", "standard_dictionary_numbers_extended.jl")) include(joinpath(@__DIR__, "en", "large_standard_dictionary_numbers_extended.jl")) include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl")) + +# utils +include(joinpath(@__DIR__, "en", "utils.jl")) # convert a value < 100 to English. function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # define scale type if number < 20 - word = _small_numbers[number + 1] - - return word + return _small_numbers[number + 1] end - v = 0 - while v < length(_tens) - d_cap = _tens[v + 1] - d_number = BigInt(20 + 10 * v) + for (v, d_cap) in enumerate(_tens) + d_number = BigInt(20 + 10 * (v - 1)) if d_number + 10 > number if mod(number, 10) ≠ 0 - word = d_cap * "-" * _small_numbers[mod(number, 10) + 1] - - return word + return d_cap * "-" * _small_numbers[mod(number, 10) + 1] end - return d_cap end - v += 1 end + + return nothing end # convert a value < 1000 to english, special cased because it is the level that excludes @@ -34,28 +32,28 @@ end # strings in the form of "forty-five hundred" if called directly. function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # define scale type - word = string() # initiate empty string + word_buf = IOBuffer() divisor = div(number, 100) modulus = mod(number, 100) if divisor > 0 - word = _small_numbers[divisor + 1] * " hundred" + print(word_buf, _small_numbers[divisor + 1], " hundred") if modulus > 0 - word = word * " " + print(word_buf, ' ') end end if british if ! iszero(divisor) && ! iszero(modulus) - word = word * "and " + print(word_buf, "and ") end end if modulus > 0 - word = word * small_convert_en(modulus, british=british, dict=dict) + print(word_buf, small_convert_en(modulus, british=british, dict=dict)) end - return word + return String(take!(word_buf)) end function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = :modern) @@ -66,42 +64,28 @@ function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = : scale_numbers = _scale_traditional_european elseif isequal(dict, :modern) else - throw(error("unrecognized dict value: $dict")) + error("Unrecognized dict value: $dict") end number = big(number) - isnegative = false - if number < 0 - isnegative = true - end - + isnegative = number < 0 number = abs(number) + if number > limit - 1 - throw(error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""")) + error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""") end if number < 100 word = small_convert_en(number, british=british, dict=dict) - - if isnegative - word = "negative " * word - end - - return word + return isnegative ? "negative " * word : word end if number < 1000 word = large_convert_en(number, british=british, dict=dict) - - if isnegative - word = "negative " * word - end - - return word + return isnegative ? "negative " * word : word end - v = 0 - while v ≤ length(scale_numbers) + for v in 0:length(scale_numbers) d_idx = v d_number = BigInt(round(big(1000)^v)) @@ -114,34 +98,33 @@ function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = : word = word * ", " * spelled_out_en(r, british=british, dict=dict) end - if isnegative - word = "negative " * word - end - - return word + return isnegative ? "negative " * word : word end - - v += 1 end + + error("Unreachable") end # Need to print ordinal numbers for the irrational printing function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) s = spelled_out_en(number, british = british, dict = dict) - - lastword = split(s)[end] - redolast = split(lastword, "-")[end] + + # lastword = split(s)[end] + # redolast = split(lastword, "-")[end] + lastword = lastsplit(isspace, s) + redolast = lastsplit('-', lastword) + if redolast != lastword - lastsplit = "-" + _lastsplit = '-' word = redolast else - lastsplit = " " + _lastsplit = ' ' word = lastword end - firstpart = reverse(split(reverse(s), lastsplit, limit = 2)[end]) - firstpart = (firstpart == word) ? string() : firstpart * lastsplit + firstpart = reverse(split(reverse(s), _lastsplit, limit = 2)[end]) + firstpart = (firstpart == word) ? "" : firstpart * _lastsplit if haskey(irregular, word) word = irregular[word] diff --git a/src/en/ordinal_dictionaries.jl b/src/en/ordinal_dictionaries.jl index 265f6db5..c9ac983e 100644 --- a/src/en/ordinal_dictionaries.jl +++ b/src/en/ordinal_dictionaries.jl @@ -2,3 +2,6 @@ const irregular = Dict("one" => "first", "two" => "second", "three" => "third", "eight" => "eighth", "nine" => "ninth", "twelve" => "twelfth") const suffix = "th" const ysuffix = "ieth" + +ordinal(n::Integer) = + string(n, (11 <= mod(n, 100) <= 13) ? "th" : (["th", "st", "nd", "rd", "th"][min(mod1(n, 10) + 1, 5)])) diff --git a/src/en/utils.jl b/src/en/utils.jl new file mode 100644 index 00000000..2053b11b --- /dev/null +++ b/src/en/utils.jl @@ -0,0 +1,23 @@ +function lastsplit(predicate, s::S) where {S <: AbstractString} + i = findlast(predicate, s) + return isnothing(i) ? SubString(s) : SubString(s, nextind(s, i)) +end + +Base.findall(c::Char, s::S) where {S <: AbstractString} = + Int[only(i) for i in findall(string(c), s)] + +function ithsplit(predicate, s::S, i::Int) where {S <: AbstractString} + j = 1 # firstindex(s) + for _ in 1:i + k = findnext(predicate, s, j) + if isnothing(k) + break + else + j = k + end + end + return SubString() +end + + +split(s, _lastsplit, limit = 2)[end] diff --git a/src/en_orig.jl b/src/en_orig.jl new file mode 100644 index 00000000..153c466a --- /dev/null +++ b/src/en_orig.jl @@ -0,0 +1,223 @@ +include(joinpath(@__DIR__, "en", "standard_dictionary_numbers_extended.jl")) +include(joinpath(@__DIR__, "en", "large_standard_dictionary_numbers_extended.jl")) +include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl")) + +# convert a value < 100 to English. +function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + scale_numbers = _scale_modern # define scale type + if number < 20 + word = _small_numbers[number + 1] + + return word + end + + v = 0 + while v < length(_tens) + d_cap = _tens[v + 1] + d_number = BigInt(20 + 10 * v) + + if d_number + 10 > number + if mod(number, 10) ≠ 0 + word = d_cap * "-" * _small_numbers[mod(number, 10) + 1] + + return word + end + + return d_cap + end + v += 1 + end +end + +# convert a value < 1000 to english, special cased because it is the level that excludes +# the < 100 special case. The rest are more general. This also allows you to get +# strings in the form of "forty-five hundred" if called directly. +function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + scale_numbers = _scale_modern # define scale type + word = string() # initiate empty string + divisor = div(number, 100) + modulus = mod(number, 100) + + if divisor > 0 + word = _small_numbers[divisor + 1] * " hundred" + if modulus > 0 + word = word * " " + end + end + + if british + if ! iszero(divisor) && ! iszero(modulus) + word = word * "and " + end + end + + if modulus > 0 + word = word * small_convert_en(modulus, british=british, dict=dict) + end + + return word +end + +function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + scale_numbers = _scale_modern # default to :modern + if isequal(dict, :british) + scale_numbers = _scale_traditional_british + elseif isequal(dict, :european) + scale_numbers = _scale_traditional_european + elseif isequal(dict, :modern) + else + throw(error("unrecognized dict value: $dict")) + end + + number = big(number) + isnegative = false + if number < 0 + isnegative = true + end + + number = abs(number) + if number > limit - 1 + throw(error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""")) + end + + if number < 100 + word = small_convert_en(number, british=british, dict=dict) + + if isnegative + word = "negative " * word + end + + return word + end + + if number < 1000 + word = large_convert_en(number, british=british, dict=dict) + + if isnegative + word = "negative " * word + end + + return word + end + + v = 0 + while v ≤ length(scale_numbers) + d_idx = v + d_number = BigInt(round(big(1000)^v)) + + if d_number > number + modulus = BigInt(big(1000)^(d_idx - 1)) + l, r = divrem(number, modulus) + word = large_convert_en(l, british=british, dict=dict) * " " * scale_numbers[d_idx - 1] + + if r > 0 + word = word * ", " * spelled_out_en(r, british=british, dict=dict) + end + + if isnegative + word = "negative " * word + end + + return word + end + + v += 1 + end +end + +# Need to print ordinal numbers for the irrational printing +function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + s = spelled_out_en(number, british = british, dict = dict) + + lastword = split(s)[end] + redolast = split(lastword, "-")[end] + + if redolast != lastword + lastsplit = "-" + word = redolast + else + lastsplit = " " + word = lastword + end + + firstpart = reverse(split(reverse(s), lastsplit, limit = 2)[end]) + firstpart = (firstpart == word) ? string() : firstpart * lastsplit + + if haskey(irregular, word) + word = irregular[word] + elseif word[end] == 'y' + word = word[1:end-1] * ysuffix + else + word = word * suffix + end + + return firstpart * word +end + +# This method is an internal method used for spelling out floats +function decimal_convert_en(number::AbstractString; british::Bool = false, dict::Symbol = :modern) + # decimal, whole = modf(number) + # whole = round(BigInt, whole) + whole, decimal = split(number, ".") + word = spelled_out_en(parse(BigInt, whole), british=british, dict=dict) * string(" point") + # word = spelled_out_en(whole, british=british, dict=dict) * string(" point") + + for i in decimal + word = word * " " * _small_number_dictionary[i] + end + + return word +end + +function spelled_out_en(number::AbstractFloat; british::Bool = false, dict::Symbol = :modern) + str_number = format(number) + if occursin('.', str_number) + _decimal, _ = modf(Dec64(number)) + _length_of_presicion = length(string(_decimal)) - 2 # (ndigits(_whole) + 1) + number = format(number, precision = _length_of_presicion) + else + # It is an integer is scientific notation, treat normally without decimal precision considerations + # E.g., 1e10 should be parsed as an integer (as should 1.0e10) + number = parse(BigInt, str_number) + end + + if isa(number, AbstractString) + # if the number is a string then it is a decimal, which is formatted precisely + # for correct precision with decimal places + return decimal_convert_en(number, british = british, dict = dict) + elseif isinteger(number) + # otherwise, it is an integer + return spelled_out_en(number, british = british, dict = dict) + else + throw(error("Cannot parse type $(typeof(number)). Please make an issue.")) + end + + # should never get here + return nothing +end + +# Spell out complex numbers +function spelled_out_en(number::Complex; british::Bool = false, dict::Symbol = :modern) + return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict=dict) * " imaginaries" +end + +function spelled_out_en(number::Rational; british::Bool = false, dict::Symbol = :modern) + _num, _den = number.num, number.den + + # return the number itself if the denomimator is one + isone(_den) && return spelled_out_en(_num, british = british, dict = dict) + + word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict) + + # account for pluralisation + return isone(_num) ? word : word * "s" +end + +function spelled_out_en(number::AbstractIrrational; british::Bool = false, dict::Symbol = :modern) + throw(error("Please round the input number, as we support floating point printing but cannot support irrational printing.")) +end + +# Fallback method if we do not know how to handle the input +function spelled_out_en(number; british::Bool = false, dict::Symbol = :modern) + throw(error("Cannot parse type $(typeof(number)). Please make an issue.")) +end diff --git a/test/Project.toml b/test/Project.toml deleted file mode 100644 index 28bcb829..00000000 --- a/test/Project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[deps] -DecFP = "55939f99-70c6-5e9b-8bb0-5071ed7d61fd" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index 666282f8..a3bc6e70 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,3 @@ -# include(joinpath(dirname(dirname(@__FILE__)), "src", "SpelledOut.jl")); using .SpelledOut using Test using SpelledOut @@ -41,4 +40,4 @@ end end -nothing + From c0f06645659e70374d587e5475df15859417ecda Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 01:21:15 +1200 Subject: [PATCH 02/13] Removed non-function test line --- src/en/utils.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/en/utils.jl b/src/en/utils.jl index 2053b11b..e0424407 100644 --- a/src/en/utils.jl +++ b/src/en/utils.jl @@ -19,5 +19,4 @@ function ithsplit(predicate, s::S, i::Int) where {S <: AbstractString} return SubString() end - -split(s, _lastsplit, limit = 2)[end] +# split(s, _lastsplit, limit = 2)[end] From 99fbc78fdd205f8c061e57efadbfafdb58d23d13 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 02:23:28 +1200 Subject: [PATCH 03/13] Added benchmark testing script --- test/benchmark.jl | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 test/benchmark.jl diff --git a/test/benchmark.jl b/test/benchmark.jl new file mode 100644 index 00000000..9acc3c3d --- /dev/null +++ b/test/benchmark.jl @@ -0,0 +1,4 @@ +using SpelledOut +using BenchmarkTools + +@btime spelled_out($123456789, lang = $:en_UK) From 22e277347c20cd4452dd6ccbe06294d32c26c42e Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 02:23:56 +1200 Subject: [PATCH 04/13] Prefer buffers over strings --- src/en.jl | 52 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/en.jl b/src/en.jl index 79a888e4..191ceb0b 100644 --- a/src/en.jl +++ b/src/en.jl @@ -56,7 +56,7 @@ function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = return String(take!(word_buf)) end -function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = :modern) +function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # default to :modern if isequal(dict, :british) scale_numbers = _scale_traditional_british @@ -67,24 +67,33 @@ function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = : error("Unrecognized dict value: $dict") end - number = big(number) - isnegative = number < 0 + word_buf = IOBuffer() + if number_orig < 0 + print(word_buf, "negative ") + end + + number = big(number_orig) number = abs(number) if number > limit - 1 error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""") end + if number < 100 word = small_convert_en(number, british=british, dict=dict) - return isnegative ? "negative " * word : word + print(word_buf, word) + return String(take!(word_buf)) end if number < 1000 word = large_convert_en(number, british=british, dict=dict) - return isnegative ? "negative " * word : word + print(word_buf, word) + return String(take!(word_buf)) end + tmp_word_buf = IOBuffer() + for v in 0:length(scale_numbers) d_idx = v d_number = BigInt(round(big(1000)^v)) @@ -92,13 +101,12 @@ function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = : if d_number > number modulus = BigInt(big(1000)^(d_idx - 1)) l, r = divrem(number, modulus) - word = large_convert_en(l, british=british, dict=dict) * " " * scale_numbers[d_idx - 1] - if r > 0 - word = word * ", " * spelled_out_en(r, british=british, dict=dict) - end + word = large_convert_en(l, british=british, dict=dict) + print(word_buf, word, " ", scale_numbers[d_idx - 1]) + r > 0 && print(word_buf, ", ", spelled_out_en(r, british=british, dict=dict)) - return isnegative ? "negative " * word : word + return String(take!(word_buf)) end end @@ -109,11 +117,8 @@ end function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) s = spelled_out_en(number, british = british, dict = dict) - # lastword = split(s)[end] - # redolast = split(lastword, "-")[end] lastword = lastsplit(isspace, s) redolast = lastsplit('-', lastword) - if redolast != lastword _lastsplit = '-' @@ -122,19 +127,22 @@ function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = _lastsplit = ' ' word = lastword end - - firstpart = reverse(split(reverse(s), _lastsplit, limit = 2)[end]) - firstpart = (firstpart == word) ? "" : firstpart * _lastsplit - + + word_buf = IOBuffer() + firstpart = reverse(last(split(reverse(s), _lastsplit, limit = 2))) + if firstpart != word + print(word_buf, firstpart, _lastsplit) + end + if haskey(irregular, word) - word = irregular[word] + print(word_buf, irregular[word]) elseif word[end] == 'y' - word = word[1:end-1] * ysuffix + print(word_buf, word[1:end-1], ysuffix) else - word = word * suffix + print(word_buf, word, suffix) end - - return firstpart * word + + return String(take!(word_buf)) end # This method is an internal method used for spelling out floats From 8c1eabe5dc13753f0a6c38436316db0e769cc5d6 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 03:01:03 +1200 Subject: [PATCH 05/13] Use IOBuffer in small convert --- src/en.jl | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/en.jl b/src/en.jl index 191ceb0b..1cf41cc1 100644 --- a/src/en.jl +++ b/src/en.jl @@ -13,14 +13,16 @@ function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = return _small_numbers[number + 1] end - for (v, d_cap) in enumerate(_tens) + word_buf = IOBuffer() + for (v, d̂) in enumerate(_tens) d_number = BigInt(20 + 10 * (v - 1)) if d_number + 10 > number if mod(number, 10) ≠ 0 - return d_cap * "-" * _small_numbers[mod(number, 10) + 1] + print(word_buf, d̂, '-', _small_numbers[mod(number, 10) + 1]) + return String(take!(word_buf)) end - return d_cap + return d̂ end end @@ -30,6 +32,7 @@ end # convert a value < 1000 to english, special cased because it is the level that excludes # the < 100 special case. The rest are more general. This also allows you to get # strings in the form of "forty-five hundred" if called directly. +# TODO: make more efficient (#2) function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # define scale type word_buf = IOBuffer() @@ -37,25 +40,26 @@ function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = modulus = mod(number, 100) if divisor > 0 - print(word_buf, _small_numbers[divisor + 1], " hundred") + write(word_buf, _small_numbers[divisor + 1], " hundred") if modulus > 0 - print(word_buf, ' ') + write(word_buf, ' ') end end if british - if ! iszero(divisor) && ! iszero(modulus) - print(word_buf, "and ") + if !iszero(divisor) && !iszero(modulus) + write(word_buf, "and ") end end if modulus > 0 - print(word_buf, small_convert_en(modulus, british=british, dict=dict)) + write(word_buf, small_convert_en(modulus, british=british, dict=dict)) end return String(take!(word_buf)) end +# TODO: make more efficient (#1) function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # default to :modern if isequal(dict, :british) @@ -69,7 +73,7 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo word_buf = IOBuffer() if number_orig < 0 - print(word_buf, "negative ") + write(word_buf, "negative ") end number = big(number_orig) @@ -82,13 +86,13 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo if number < 100 word = small_convert_en(number, british=british, dict=dict) - print(word_buf, word) + write(word_buf, word) return String(take!(word_buf)) end if number < 1000 word = large_convert_en(number, british=british, dict=dict) - print(word_buf, word) + write(word_buf, word) return String(take!(word_buf)) end @@ -103,8 +107,8 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo l, r = divrem(number, modulus) word = large_convert_en(l, british=british, dict=dict) - print(word_buf, word, " ", scale_numbers[d_idx - 1]) - r > 0 && print(word_buf, ", ", spelled_out_en(r, british=british, dict=dict)) + write(word_buf, word, " ", scale_numbers[d_idx - 1]) + r > 0 && write(word_buf, ", ", spelled_out_en(r, british=british, dict=dict)) return String(take!(word_buf)) end @@ -131,15 +135,15 @@ function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = word_buf = IOBuffer() firstpart = reverse(last(split(reverse(s), _lastsplit, limit = 2))) if firstpart != word - print(word_buf, firstpart, _lastsplit) + write(word_buf, firstpart, _lastsplit) end if haskey(irregular, word) - print(word_buf, irregular[word]) + write(word_buf, irregular[word]) elseif word[end] == 'y' - print(word_buf, word[1:end-1], ysuffix) + write(word_buf, word[1:end-1], ysuffix) else - print(word_buf, word, suffix) + write(word_buf, word, suffix) end return String(take!(word_buf)) From 3175b96837a586ccb2a8670eec4cb0b5b2e70355 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 11:17:00 +1200 Subject: [PATCH 06/13] Work mostly with IOBuffers Instead of creating IOBuffers within each function, pass around IOBuffers to modifying functions and only take from them at the end of the process! This vastly reduces the allocations as we are no longer creating a new IOBuffer within each function. However, it is still not as fast as it was (but its memory usage is better). --- src/en.jl | 74 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/en.jl b/src/en.jl index 1cf41cc1..3488ee61 100644 --- a/src/en.jl +++ b/src/en.jl @@ -7,60 +7,70 @@ include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl")) include(joinpath(@__DIR__, "en", "utils.jl")) # convert a value < 100 to English. -function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) +function _small_convert_en!(io::IOBuffer, number::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # define scale type if number < 20 - return _small_numbers[number + 1] + write(io, _small_numbers[number + 1]) + return io end - word_buf = IOBuffer() for (v, d̂) in enumerate(_tens) d_number = BigInt(20 + 10 * (v - 1)) if d_number + 10 > number if mod(number, 10) ≠ 0 - print(word_buf, d̂, '-', _small_numbers[mod(number, 10) + 1]) - return String(take!(word_buf)) + write(io, d̂, '-', _small_numbers[mod(number, 10) + 1]) + return io end - return d̂ + write(io, d̂) + return io end end - return nothing + return io +end + +function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + word_buf = IOBuffer() + _small_convert_en!(word_buf, number; british=british, dict=dict) + return String(take!(word_buf)) end # convert a value < 1000 to english, special cased because it is the level that excludes # the < 100 special case. The rest are more general. This also allows you to get # strings in the form of "forty-five hundred" if called directly. -# TODO: make more efficient (#2) -function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) +function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # define scale type - word_buf = IOBuffer() divisor = div(number, 100) modulus = mod(number, 100) if divisor > 0 - write(word_buf, _small_numbers[divisor + 1], " hundred") + write(io, _small_numbers[divisor + 1], " hundred") if modulus > 0 - write(word_buf, ' ') + write(io, ' ') end end if british if !iszero(divisor) && !iszero(modulus) - write(word_buf, "and ") + write(io, "and ") end end if modulus > 0 - write(word_buf, small_convert_en(modulus, british=british, dict=dict)) + _small_convert_en!(io, modulus, british=british, dict=dict) end + return io +end + +function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) + word_buf = IOBuffer() + _large_convert_en!(word_buf, number; british=british, dict=dict) return String(take!(word_buf)) end -# TODO: make more efficient (#1) -function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern) +function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # default to :modern if isequal(dict, :british) scale_numbers = _scale_traditional_british @@ -71,9 +81,8 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo error("Unrecognized dict value: $dict") end - word_buf = IOBuffer() if number_orig < 0 - write(word_buf, "negative ") + write(io, "negative ") end number = big(number_orig) @@ -85,19 +94,15 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo if number < 100 - word = small_convert_en(number, british=british, dict=dict) - write(word_buf, word) - return String(take!(word_buf)) + _small_convert_en!(io, number, british=british, dict=dict) + return io end if number < 1000 - word = large_convert_en(number, british=british, dict=dict) - write(word_buf, word) - return String(take!(word_buf)) + _large_convert_en!(io, number, british=british, dict=dict) + return io end - tmp_word_buf = IOBuffer() - for v in 0:length(scale_numbers) d_idx = v d_number = BigInt(round(big(1000)^v)) @@ -106,17 +111,26 @@ function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbo modulus = BigInt(big(1000)^(d_idx - 1)) l, r = divrem(number, modulus) - word = large_convert_en(l, british=british, dict=dict) - write(word_buf, word, " ", scale_numbers[d_idx - 1]) - r > 0 && write(word_buf, ", ", spelled_out_en(r, british=british, dict=dict)) + _large_convert_en!(io, l, british=british, dict=dict) + write(io, " ", scale_numbers[d_idx - 1]) + if r > 0 + write(io, ", ") + _spelled_out_en!(io, r, british=british, dict=dict) + end - return String(take!(word_buf)) + return io end end error("Unreachable") end +function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern) + word_buf = IOBuffer() + _spelled_out_en!(word_buf, number_orig; british=british, dict=dict) + return String(take!(word_buf)) +end + # Need to print ordinal numbers for the irrational printing function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) s = spelled_out_en(number, british = british, dict = dict) From b40d83524fa99042babe102b055345fcd88ddfa5 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 11:49:10 +1200 Subject: [PATCH 07/13] Remove unused kwargs to lesser functions --- src/en.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/en.jl b/src/en.jl index 3488ee61..00cd9d13 100644 --- a/src/en.jl +++ b/src/en.jl @@ -7,8 +7,7 @@ include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl")) include(joinpath(@__DIR__, "en", "utils.jl")) # convert a value < 100 to English. -function _small_convert_en!(io::IOBuffer, number::Integer; british::Bool = false, dict::Symbol = :modern) - scale_numbers = _scale_modern # define scale type +function _small_convert_en!(io::IOBuffer, number::Integer) if number < 20 write(io, _small_numbers[number + 1]) return io @@ -19,7 +18,8 @@ function _small_convert_en!(io::IOBuffer, number::Integer; british::Bool = false if d_number + 10 > number if mod(number, 10) ≠ 0 - write(io, d̂, '-', _small_numbers[mod(number, 10) + 1]) + sn = _small_numbers[mod(number, 10) + 1] + write(io, d̂, '-', sn) return io end write(io, d̂) @@ -30,17 +30,16 @@ function _small_convert_en!(io::IOBuffer, number::Integer; british::Bool = false return io end -function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) +function small_convert_en(number::Integer) word_buf = IOBuffer() - _small_convert_en!(word_buf, number; british=british, dict=dict) + _small_convert_en!(word_buf, number) return String(take!(word_buf)) end # convert a value < 1000 to english, special cased because it is the level that excludes # the < 100 special case. The rest are more general. This also allows you to get # strings in the form of "forty-five hundred" if called directly. -function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false, dict::Symbol = :modern) - scale_numbers = _scale_modern # define scale type +function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false) divisor = div(number, 100) modulus = mod(number, 100) @@ -58,15 +57,15 @@ function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false end if modulus > 0 - _small_convert_en!(io, modulus, british=british, dict=dict) + _small_convert_en!(io, modulus) end return io end -function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) +function large_convert_en(number::Integer; british::Bool = false) word_buf = IOBuffer() - _large_convert_en!(word_buf, number; british=british, dict=dict) + _large_convert_en!(word_buf, number; british=british) return String(take!(word_buf)) end @@ -77,6 +76,7 @@ function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = fa elseif isequal(dict, :european) scale_numbers = _scale_traditional_european elseif isequal(dict, :modern) + # This is the default condition else error("Unrecognized dict value: $dict") end @@ -94,12 +94,12 @@ function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = fa if number < 100 - _small_convert_en!(io, number, british=british, dict=dict) + _small_convert_en!(io, number) return io end if number < 1000 - _large_convert_en!(io, number, british=british, dict=dict) + _large_convert_en!(io, number, british=british) return io end @@ -111,7 +111,7 @@ function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = fa modulus = BigInt(big(1000)^(d_idx - 1)) l, r = divrem(number, modulus) - _large_convert_en!(io, l, british=british, dict=dict) + _large_convert_en!(io, l, british=british) write(io, " ", scale_numbers[d_idx - 1]) if r > 0 write(io, ", ") From a9403ed68aeeb246334164fe92b5822fb9fd9119 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Mon, 27 Jun 2022 11:57:48 +1200 Subject: [PATCH 08/13] Only work with bigints after we check if number is small --- src/en.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/en.jl b/src/en.jl index 00cd9d13..c42b4669 100644 --- a/src/en.jl +++ b/src/en.jl @@ -69,7 +69,7 @@ function large_convert_en(number::Integer; british::Bool = false) return String(take!(word_buf)) end -function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = false, dict::Symbol = :modern) +function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = false, dict::Symbol = :modern) scale_numbers = _scale_modern # default to :modern if isequal(dict, :british) scale_numbers = _scale_traditional_british @@ -81,28 +81,28 @@ function _spelled_out_en!(io::IOBuffer, number_orig::Integer; british::Bool = fa error("Unrecognized dict value: $dict") end - if number_orig < 0 + if number_norm < 0 write(io, "negative ") end - - number = big(number_orig) - number = abs(number) - if number > limit - 1 + if number_norm > limit - 1 error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""") end - if number < 100 - _small_convert_en!(io, number) + if number_norm < 100 + _small_convert_en!(io, number_norm) return io end - if number < 1000 - _large_convert_en!(io, number, british=british) + if number_norm < 1000 + _large_convert_en!(io, number_norm, british=british) return io end + number = big(number_norm) + number = abs(number) + for v in 0:length(scale_numbers) d_idx = v d_number = BigInt(round(big(1000)^v)) From 00f458a4fcb01e6c229388fe67315f5376e417b9 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Tue, 28 Jun 2022 02:09:11 +1200 Subject: [PATCH 09/13] Favour `print(io, ...)` over `write(io, ...)` --- src/en.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/en.jl b/src/en.jl index c42b4669..c3b3caec 100644 --- a/src/en.jl +++ b/src/en.jl @@ -9,7 +9,7 @@ include(joinpath(@__DIR__, "en", "utils.jl")) # convert a value < 100 to English. function _small_convert_en!(io::IOBuffer, number::Integer) if number < 20 - write(io, _small_numbers[number + 1]) + print(io, _small_numbers[number + 1]) return io end @@ -19,10 +19,10 @@ function _small_convert_en!(io::IOBuffer, number::Integer) if d_number + 10 > number if mod(number, 10) ≠ 0 sn = _small_numbers[mod(number, 10) + 1] - write(io, d̂, '-', sn) + print(io, d̂, '-', sn) return io end - write(io, d̂) + print(io, d̂) return io end end @@ -44,15 +44,15 @@ function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false modulus = mod(number, 100) if divisor > 0 - write(io, _small_numbers[divisor + 1], " hundred") + print(io, _small_numbers[divisor + 1], " hundred") if modulus > 0 - write(io, ' ') + print(io, ' ') end end if british if !iszero(divisor) && !iszero(modulus) - write(io, "and ") + print(io, "and ") end end @@ -82,7 +82,7 @@ function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = fa end if number_norm < 0 - write(io, "negative ") + print(io, "negative ") end if number_norm > limit - 1 @@ -112,9 +112,9 @@ function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = fa l, r = divrem(number, modulus) _large_convert_en!(io, l, british=british) - write(io, " ", scale_numbers[d_idx - 1]) + print(io, " ", scale_numbers[d_idx - 1]) if r > 0 - write(io, ", ") + print(io, ", ") _spelled_out_en!(io, r, british=british, dict=dict) end @@ -149,15 +149,15 @@ function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = word_buf = IOBuffer() firstpart = reverse(last(split(reverse(s), _lastsplit, limit = 2))) if firstpart != word - write(word_buf, firstpart, _lastsplit) + print(word_buf, firstpart, _lastsplit) end if haskey(irregular, word) - write(word_buf, irregular[word]) + print(word_buf, irregular[word]) elseif word[end] == 'y' - write(word_buf, word[1:end-1], ysuffix) + print(word_buf, word[1:end-1], ysuffix) else - write(word_buf, word, suffix) + print(word_buf, word, suffix) end return String(take!(word_buf)) From 7fb5af2bb48a0220891864272b89992e4311169e Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Tue, 28 Jun 2022 02:22:59 +1200 Subject: [PATCH 10/13] Reduced liberal use of big integers --- src/en.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/en.jl b/src/en.jl index c3b3caec..3e38d6c7 100644 --- a/src/en.jl +++ b/src/en.jl @@ -13,13 +13,15 @@ function _small_convert_en!(io::IOBuffer, number::Integer) return io end + m = mod(number, 10) + for (v, d̂) in enumerate(_tens) - d_number = BigInt(20 + 10 * (v - 1)) + d = 20 + 10 * (v - 1) - if d_number + 10 > number - if mod(number, 10) ≠ 0 - sn = _small_numbers[mod(number, 10) + 1] - print(io, d̂, '-', sn) + if d + 10 > number + if m ≠ 0 + n = _small_numbers[m + 1] + print(io, d̂, '-', n) return io end print(io, d̂) @@ -100,15 +102,14 @@ function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = fa return io end - number = big(number_norm) - number = abs(number) + number = abs(number_norm) for v in 0:length(scale_numbers) d_idx = v - d_number = BigInt(round(big(1000)^v)) + d_number = round(big(1000)^v) if d_number > number - modulus = BigInt(big(1000)^(d_idx - 1)) + modulus = big(1000)^(d_idx - 1) l, r = divrem(number, modulus) _large_convert_en!(io, l, british=british) From d2f1fefdd02d159419a48c5447f08100d08f8ecb Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Tue, 28 Jun 2022 02:44:04 +1200 Subject: [PATCH 11/13] Better string manipulation and use buf for niche convert funcs Instead of using a weird `reverse` method for string manipulation in printing of ordinals, we use a util function. Also, use `IOBuffer` where practical for alt convert methods --- src/en.jl | 47 +++++++++++++++++++++++++---------------------- src/en/utils.jl | 5 +++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/en.jl b/src/en.jl index 3e38d6c7..fb8aec81 100644 --- a/src/en.jl +++ b/src/en.jl @@ -67,7 +67,7 @@ end function large_convert_en(number::Integer; british::Bool = false) word_buf = IOBuffer() - _large_convert_en!(word_buf, number; british=british) + _large_convert_en!(word_buf, number; british = british) return String(take!(word_buf)) end @@ -98,7 +98,7 @@ function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = fa end if number_norm < 1000 - _large_convert_en!(io, number_norm, british=british) + _large_convert_en!(io, number_norm, british = british) return io end @@ -112,11 +112,11 @@ function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = fa modulus = big(1000)^(d_idx - 1) l, r = divrem(number, modulus) - _large_convert_en!(io, l, british=british) + _large_convert_en!(io, l, british = british) print(io, " ", scale_numbers[d_idx - 1]) if r > 0 print(io, ", ") - _spelled_out_en!(io, r, british=british, dict=dict) + _spelled_out_en!(io, r, british = british, dict = dict) end return io @@ -128,13 +128,15 @@ end function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern) word_buf = IOBuffer() - _spelled_out_en!(word_buf, number_orig; british=british, dict=dict) + _spelled_out_en!(word_buf, number_orig; british = british, dict = dict) return String(take!(word_buf)) end # Need to print ordinal numbers for the irrational printing function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) - s = spelled_out_en(number, british = british, dict = dict) + word_buf = IOBuffer() + _spelled_out_en!(word_buf, number, british = british, dict = dict) + s = String(take!(word_buf)) lastword = lastsplit(isspace, s) redolast = lastsplit('-', lastword) @@ -147,8 +149,8 @@ function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = word = lastword end - word_buf = IOBuffer() - firstpart = reverse(last(split(reverse(s), _lastsplit, limit = 2))) + firstpart = firstlastsplit(_lastsplit, s) + if firstpart != word print(word_buf, firstpart, _lastsplit) end @@ -169,14 +171,15 @@ function decimal_convert_en(number::AbstractString; british::Bool = false, dict: # decimal, whole = modf(number) # whole = round(BigInt, whole) whole, decimal = split(number, ".") - word = spelled_out_en(parse(BigInt, whole), british=british, dict=dict) * string(" point") - # word = spelled_out_en(whole, british=british, dict=dict) * string(" point") + word_buf = IOBuffer() + _spelled_out_en!(word_buf, parse(BigInt, whole), british = british, dict = dict) + print(word_buf, " point") for i in decimal - word = word * " " * _small_number_dictionary[i] + print(word_buf, ' ', _small_number_dictionary[i]) end - return word + return String(take!(word_buf)) end function spelled_out_en(number::AbstractFloat; british::Bool = false, dict::Symbol = :modern) @@ -208,19 +211,19 @@ end # Spell out complex numbers function spelled_out_en(number::Complex; british::Bool = false, dict::Symbol = :modern) - return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict=dict) * " imaginaries" + return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict = dict) * " imaginaries" end function spelled_out_en(number::Rational; british::Bool = false, dict::Symbol = :modern) - _num, _den = number.num, number.den - - # return the number itself if the denomimator is one - isone(_den) && return spelled_out_en(_num, british = british, dict = dict) - - word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict) - - # account for pluralisation - return isone(_num) ? word : word * "s" + _num, _den = number.num, number.den + + # return the number itself if the denomimator is one + isone(_den) && return spelled_out_en(_num, british = british, dict = dict) + + word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict) + + # account for pluralisation + return isone(_num) ? word : word * "s" end function spelled_out_en(number::AbstractIrrational; british::Bool = false, dict::Symbol = :modern) diff --git a/src/en/utils.jl b/src/en/utils.jl index e0424407..b2bdde69 100644 --- a/src/en/utils.jl +++ b/src/en/utils.jl @@ -3,6 +3,11 @@ function lastsplit(predicate, s::S) where {S <: AbstractString} return isnothing(i) ? SubString(s) : SubString(s, nextind(s, i)) end +function firstlastsplit(predicate, s::S) where {S <: AbstractString} + i = findlast(predicate, s) + return isnothing(i) ? SubString(s) : SubString(s, 1, prevind(s, i)) +end + Base.findall(c::Char, s::S) where {S <: AbstractString} = Int[only(i) for i in findall(string(c), s)] From a8f2a970e708ec6cf5c05ede9f76f0045b09d4a3 Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Tue, 28 Jun 2022 03:07:39 +1200 Subject: [PATCH 12/13] Remove original copy backup --- src/en_orig.jl | 223 ------------------------------------------------- 1 file changed, 223 deletions(-) delete mode 100644 src/en_orig.jl diff --git a/src/en_orig.jl b/src/en_orig.jl deleted file mode 100644 index 153c466a..00000000 --- a/src/en_orig.jl +++ /dev/null @@ -1,223 +0,0 @@ -include(joinpath(@__DIR__, "en", "standard_dictionary_numbers_extended.jl")) -include(joinpath(@__DIR__, "en", "large_standard_dictionary_numbers_extended.jl")) -include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl")) - -# convert a value < 100 to English. -function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) - scale_numbers = _scale_modern # define scale type - if number < 20 - word = _small_numbers[number + 1] - - return word - end - - v = 0 - while v < length(_tens) - d_cap = _tens[v + 1] - d_number = BigInt(20 + 10 * v) - - if d_number + 10 > number - if mod(number, 10) ≠ 0 - word = d_cap * "-" * _small_numbers[mod(number, 10) + 1] - - return word - end - - return d_cap - end - v += 1 - end -end - -# convert a value < 1000 to english, special cased because it is the level that excludes -# the < 100 special case. The rest are more general. This also allows you to get -# strings in the form of "forty-five hundred" if called directly. -function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern) - scale_numbers = _scale_modern # define scale type - word = string() # initiate empty string - divisor = div(number, 100) - modulus = mod(number, 100) - - if divisor > 0 - word = _small_numbers[divisor + 1] * " hundred" - if modulus > 0 - word = word * " " - end - end - - if british - if ! iszero(divisor) && ! iszero(modulus) - word = word * "and " - end - end - - if modulus > 0 - word = word * small_convert_en(modulus, british=british, dict=dict) - end - - return word -end - -function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = :modern) - scale_numbers = _scale_modern # default to :modern - if isequal(dict, :british) - scale_numbers = _scale_traditional_british - elseif isequal(dict, :european) - scale_numbers = _scale_traditional_european - elseif isequal(dict, :modern) - else - throw(error("unrecognized dict value: $dict")) - end - - number = big(number) - isnegative = false - if number < 0 - isnegative = true - end - - number = abs(number) - if number > limit - 1 - throw(error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""")) - end - - if number < 100 - word = small_convert_en(number, british=british, dict=dict) - - if isnegative - word = "negative " * word - end - - return word - end - - if number < 1000 - word = large_convert_en(number, british=british, dict=dict) - - if isnegative - word = "negative " * word - end - - return word - end - - v = 0 - while v ≤ length(scale_numbers) - d_idx = v - d_number = BigInt(round(big(1000)^v)) - - if d_number > number - modulus = BigInt(big(1000)^(d_idx - 1)) - l, r = divrem(number, modulus) - word = large_convert_en(l, british=british, dict=dict) * " " * scale_numbers[d_idx - 1] - - if r > 0 - word = word * ", " * spelled_out_en(r, british=british, dict=dict) - end - - if isnegative - word = "negative " * word - end - - return word - end - - v += 1 - end -end - -# Need to print ordinal numbers for the irrational printing -function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern) - s = spelled_out_en(number, british = british, dict = dict) - - lastword = split(s)[end] - redolast = split(lastword, "-")[end] - - if redolast != lastword - lastsplit = "-" - word = redolast - else - lastsplit = " " - word = lastword - end - - firstpart = reverse(split(reverse(s), lastsplit, limit = 2)[end]) - firstpart = (firstpart == word) ? string() : firstpart * lastsplit - - if haskey(irregular, word) - word = irregular[word] - elseif word[end] == 'y' - word = word[1:end-1] * ysuffix - else - word = word * suffix - end - - return firstpart * word -end - -# This method is an internal method used for spelling out floats -function decimal_convert_en(number::AbstractString; british::Bool = false, dict::Symbol = :modern) - # decimal, whole = modf(number) - # whole = round(BigInt, whole) - whole, decimal = split(number, ".") - word = spelled_out_en(parse(BigInt, whole), british=british, dict=dict) * string(" point") - # word = spelled_out_en(whole, british=british, dict=dict) * string(" point") - - for i in decimal - word = word * " " * _small_number_dictionary[i] - end - - return word -end - -function spelled_out_en(number::AbstractFloat; british::Bool = false, dict::Symbol = :modern) - str_number = format(number) - if occursin('.', str_number) - _decimal, _ = modf(Dec64(number)) - _length_of_presicion = length(string(_decimal)) - 2 # (ndigits(_whole) + 1) - number = format(number, precision = _length_of_presicion) - else - # It is an integer is scientific notation, treat normally without decimal precision considerations - # E.g., 1e10 should be parsed as an integer (as should 1.0e10) - number = parse(BigInt, str_number) - end - - if isa(number, AbstractString) - # if the number is a string then it is a decimal, which is formatted precisely - # for correct precision with decimal places - return decimal_convert_en(number, british = british, dict = dict) - elseif isinteger(number) - # otherwise, it is an integer - return spelled_out_en(number, british = british, dict = dict) - else - throw(error("Cannot parse type $(typeof(number)). Please make an issue.")) - end - - # should never get here - return nothing -end - -# Spell out complex numbers -function spelled_out_en(number::Complex; british::Bool = false, dict::Symbol = :modern) - return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict=dict) * " imaginaries" -end - -function spelled_out_en(number::Rational; british::Bool = false, dict::Symbol = :modern) - _num, _den = number.num, number.den - - # return the number itself if the denomimator is one - isone(_den) && return spelled_out_en(_num, british = british, dict = dict) - - word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict) - - # account for pluralisation - return isone(_num) ? word : word * "s" -end - -function spelled_out_en(number::AbstractIrrational; british::Bool = false, dict::Symbol = :modern) - throw(error("Please round the input number, as we support floating point printing but cannot support irrational printing.")) -end - -# Fallback method if we do not know how to handle the input -function spelled_out_en(number; british::Bool = false, dict::Symbol = :modern) - throw(error("Cannot parse type $(typeof(number)). Please make an issue.")) -end From 9b2bc8b457ed593f9d9ad9fcc8b182fdb0eb11cc Mon Sep 17 00:00:00 2001 From: Jake Ireland Date: Tue, 28 Jun 2022 03:09:26 +1200 Subject: [PATCH 13/13] Removed no-longer-needed benchmarking file --- test/benchmark.jl | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test/benchmark.jl diff --git a/test/benchmark.jl b/test/benchmark.jl deleted file mode 100644 index 9acc3c3d..00000000 --- a/test/benchmark.jl +++ /dev/null @@ -1,4 +0,0 @@ -using SpelledOut -using BenchmarkTools - -@btime spelled_out($123456789, lang = $:en_UK)