Skip to content

Commit 7e001d5

Browse files
authored
Merge pull request #53 from jakewilliami/optimisation
Optimise English implementation
2 parents c01f8c1 + 9b2bc8b commit 7e001d5

File tree

5 files changed

+137
-98
lines changed

5 files changed

+137
-98
lines changed

src/en.jl

Lines changed: 106 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,185 @@
1+
# dictionaries
12
include(joinpath(@__DIR__, "en", "standard_dictionary_numbers_extended.jl"))
23
include(joinpath(@__DIR__, "en", "large_standard_dictionary_numbers_extended.jl"))
34
include(joinpath(@__DIR__, "en", "ordinal_dictionaries.jl"))
5+
6+
# utils
7+
include(joinpath(@__DIR__, "en", "utils.jl"))
48

59
# convert a value < 100 to English.
6-
function small_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern)
7-
scale_numbers = _scale_modern # define scale type
10+
function _small_convert_en!(io::IOBuffer, number::Integer)
811
if number < 20
9-
word = _small_numbers[number + 1]
10-
11-
return word
12+
print(io, _small_numbers[number + 1])
13+
return io
1214
end
1315

14-
v = 0
15-
while v < length(_tens)
16-
d_cap = _tens[v + 1]
17-
d_number = BigInt(20 + 10 * v)
16+
m = mod(number, 10)
17+
18+
for (v, d̂) in enumerate(_tens)
19+
d = 20 + 10 * (v - 1)
1820

19-
if d_number + 10 > number
20-
if mod(number, 10) 0
21-
word = d_cap * "-" * _small_numbers[mod(number, 10) + 1]
22-
23-
return word
21+
if d + 10 > number
22+
if m 0
23+
n = _small_numbers[m + 1]
24+
print(io, d̂, '-', n)
25+
return io
2426
end
25-
26-
return d_cap
27+
print(io, d̂)
28+
return io
2729
end
28-
v += 1
2930
end
31+
32+
return io
33+
end
34+
35+
function small_convert_en(number::Integer)
36+
word_buf = IOBuffer()
37+
_small_convert_en!(word_buf, number)
38+
return String(take!(word_buf))
3039
end
3140

3241
# convert a value < 1000 to english, special cased because it is the level that excludes
3342
# the < 100 special case. The rest are more general. This also allows you to get
3443
# strings in the form of "forty-five hundred" if called directly.
35-
function large_convert_en(number::Integer; british::Bool = false, dict::Symbol = :modern)
36-
scale_numbers = _scale_modern # define scale type
37-
word = string() # initiate empty string
44+
function _large_convert_en!(io::IOBuffer, number::Integer; british::Bool = false)
3845
divisor = div(number, 100)
3946
modulus = mod(number, 100)
4047

4148
if divisor > 0
42-
word = _small_numbers[divisor + 1] * " hundred"
49+
print(io, _small_numbers[divisor + 1], " hundred")
4350
if modulus > 0
44-
word = word * " "
51+
print(io, ' ')
4552
end
4653
end
4754

4855
if british
49-
if ! iszero(divisor) && ! iszero(modulus)
50-
word = word * "and "
56+
if !iszero(divisor) && !iszero(modulus)
57+
print(io, "and ")
5158
end
5259
end
5360

5461
if modulus > 0
55-
word = word * small_convert_en(modulus, british=british, dict=dict)
62+
_small_convert_en!(io, modulus)
5663
end
5764

58-
return word
65+
return io
5966
end
6067

61-
function spelled_out_en(number::Integer; british::Bool = false, dict::Symbol = :modern)
68+
function large_convert_en(number::Integer; british::Bool = false)
69+
word_buf = IOBuffer()
70+
_large_convert_en!(word_buf, number; british = british)
71+
return String(take!(word_buf))
72+
end
73+
74+
function _spelled_out_en!(io::IOBuffer, number_norm::Integer; british::Bool = false, dict::Symbol = :modern)
6275
scale_numbers = _scale_modern # default to :modern
6376
if isequal(dict, :british)
6477
scale_numbers = _scale_traditional_british
6578
elseif isequal(dict, :european)
6679
scale_numbers = _scale_traditional_european
6780
elseif isequal(dict, :modern)
81+
# This is the default condition
6882
else
69-
throw(error("unrecognized dict value: $dict"))
83+
error("Unrecognized dict value: $dict")
7084
end
7185

72-
number = big(number)
73-
isnegative = false
74-
if number < 0
75-
isnegative = true
86+
if number_norm < 0
87+
print(io, "negative ")
7688
end
77-
78-
number = abs(number)
79-
if number > limit - 1
80-
throw(error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!"""))
89+
90+
if number_norm > limit - 1
91+
error("""SpelledOut.jl does not support numbers larger than $(limit_str * " - 1"). Sorry about that!""")
8192
end
8293

83-
if number < 100
84-
word = small_convert_en(number, british=british, dict=dict)
85-
86-
if isnegative
87-
word = "negative " * word
88-
end
89-
90-
return word
94+
95+
if number_norm < 100
96+
_small_convert_en!(io, number_norm)
97+
return io
9198
end
9299

93-
if number < 1000
94-
word = large_convert_en(number, british=british, dict=dict)
95-
96-
if isnegative
97-
word = "negative " * word
98-
end
99-
100-
return word
100+
if number_norm < 1000
101+
_large_convert_en!(io, number_norm, british = british)
102+
return io
101103
end
102104

103-
v = 0
104-
while v length(scale_numbers)
105+
number = abs(number_norm)
106+
107+
for v in 0:length(scale_numbers)
105108
d_idx = v
106-
d_number = BigInt(round(big(1000)^v))
109+
d_number = round(big(1000)^v)
107110

108111
if d_number > number
109-
modulus = BigInt(big(1000)^(d_idx - 1))
112+
modulus = big(1000)^(d_idx - 1)
110113
l, r = divrem(number, modulus)
111-
word = large_convert_en(l, british=british, dict=dict) * " " * scale_numbers[d_idx - 1]
112114

115+
_large_convert_en!(io, l, british = british)
116+
print(io, " ", scale_numbers[d_idx - 1])
113117
if r > 0
114-
word = word * ", " * spelled_out_en(r, british=british, dict=dict)
118+
print(io, ", ")
119+
_spelled_out_en!(io, r, british = british, dict = dict)
115120
end
116121

117-
if isnegative
118-
word = "negative " * word
119-
end
120-
121-
return word
122+
return io
122123
end
123-
124-
v += 1
125124
end
125+
126+
error("Unreachable")
127+
end
128+
129+
function spelled_out_en(number_orig::Integer; british::Bool = false, dict::Symbol = :modern)
130+
word_buf = IOBuffer()
131+
_spelled_out_en!(word_buf, number_orig; british = british, dict = dict)
132+
return String(take!(word_buf))
126133
end
127134

128135
# Need to print ordinal numbers for the irrational printing
129136
function spell_ordinal_en(number::Integer; british::Bool = false, dict::Symbol = :modern)
130-
s = spelled_out_en(number, british = british, dict = dict)
131-
132-
lastword = split(s)[end]
133-
redolast = split(lastword, "-")[end]
137+
word_buf = IOBuffer()
138+
_spelled_out_en!(word_buf, number, british = british, dict = dict)
139+
s = String(take!(word_buf))
140+
141+
lastword = lastsplit(isspace, s)
142+
redolast = lastsplit('-', lastword)
134143

135144
if redolast != lastword
136-
lastsplit = "-"
145+
_lastsplit = '-'
137146
word = redolast
138147
else
139-
lastsplit = " "
148+
_lastsplit = ' '
140149
word = lastword
141150
end
151+
152+
firstpart = firstlastsplit(_lastsplit, s)
142153

143-
firstpart = reverse(split(reverse(s), lastsplit, limit = 2)[end])
144-
firstpart = (firstpart == word) ? string() : firstpart * lastsplit
145-
154+
if firstpart != word
155+
print(word_buf, firstpart, _lastsplit)
156+
end
157+
146158
if haskey(irregular, word)
147-
word = irregular[word]
159+
print(word_buf, irregular[word])
148160
elseif word[end] == 'y'
149-
word = word[1:end-1] * ysuffix
161+
print(word_buf, word[1:end-1], ysuffix)
150162
else
151-
word = word * suffix
163+
print(word_buf, word, suffix)
152164
end
153-
154-
return firstpart * word
165+
166+
return String(take!(word_buf))
155167
end
156168

157169
# This method is an internal method used for spelling out floats
158170
function decimal_convert_en(number::AbstractString; british::Bool = false, dict::Symbol = :modern)
159171
# decimal, whole = modf(number)
160172
# whole = round(BigInt, whole)
161173
whole, decimal = split(number, ".")
162-
word = spelled_out_en(parse(BigInt, whole), british=british, dict=dict) * string(" point")
163-
# word = spelled_out_en(whole, british=british, dict=dict) * string(" point")
174+
word_buf = IOBuffer()
175+
_spelled_out_en!(word_buf, parse(BigInt, whole), british = british, dict = dict)
176+
print(word_buf, " point")
164177

165178
for i in decimal
166-
word = word * " " * _small_number_dictionary[i]
179+
print(word_buf, ' ', _small_number_dictionary[i])
167180
end
168181

169-
return word
182+
return String(take!(word_buf))
170183
end
171184

172185
function spelled_out_en(number::AbstractFloat; british::Bool = false, dict::Symbol = :modern)
@@ -198,19 +211,19 @@ end
198211

199212
# Spell out complex numbers
200213
function spelled_out_en(number::Complex; british::Bool = false, dict::Symbol = :modern)
201-
return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict=dict) * " imaginaries"
214+
return spelled_out_en(real(number), british = british, dict = dict) * " and " * spelled_out_en(imag(number), british = british, dict = dict) * " imaginaries"
202215
end
203216

204217
function spelled_out_en(number::Rational; british::Bool = false, dict::Symbol = :modern)
205-
_num, _den = number.num, number.den
206-
207-
# return the number itself if the denomimator is one
208-
isone(_den) && return spelled_out_en(_num, british = british, dict = dict)
209-
210-
word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict)
211-
212-
# account for pluralisation
213-
return isone(_num) ? word : word * "s"
218+
_num, _den = number.num, number.den
219+
220+
# return the number itself if the denomimator is one
221+
isone(_den) && return spelled_out_en(_num, british = british, dict = dict)
222+
223+
word = spelled_out_en(_num, british = british, dict = dict) * " " * spell_ordinal_en(_den, british = british, dict = dict)
224+
225+
# account for pluralisation
226+
return isone(_num) ? word : word * "s"
214227
end
215228

216229
function spelled_out_en(number::AbstractIrrational; british::Bool = false, dict::Symbol = :modern)

src/en/ordinal_dictionaries.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ const irregular = Dict("one" => "first", "two" => "second", "three" => "third",
22
"eight" => "eighth", "nine" => "ninth", "twelve" => "twelfth")
33
const suffix = "th"
44
const ysuffix = "ieth"
5+
6+
ordinal(n::Integer) =
7+
string(n, (11 <= mod(n, 100) <= 13) ? "th" : (["th", "st", "nd", "rd", "th"][min(mod1(n, 10) + 1, 5)]))

src/en/utils.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function lastsplit(predicate, s::S) where {S <: AbstractString}
2+
i = findlast(predicate, s)
3+
return isnothing(i) ? SubString(s) : SubString(s, nextind(s, i))
4+
end
5+
6+
function firstlastsplit(predicate, s::S) where {S <: AbstractString}
7+
i = findlast(predicate, s)
8+
return isnothing(i) ? SubString(s) : SubString(s, 1, prevind(s, i))
9+
end
10+
11+
Base.findall(c::Char, s::S) where {S <: AbstractString} =
12+
Int[only(i) for i in findall(string(c), s)]
13+
14+
function ithsplit(predicate, s::S, i::Int) where {S <: AbstractString}
15+
j = 1 # firstindex(s)
16+
for _ in 1:i
17+
k = findnext(predicate, s, j)
18+
if isnothing(k)
19+
break
20+
else
21+
j = k
22+
end
23+
end
24+
return SubString()
25+
end
26+
27+
# split(s, _lastsplit, limit = 2)[end]

test/Project.toml

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/runtests.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# include(joinpath(dirname(dirname(@__FILE__)), "src", "SpelledOut.jl")); using .SpelledOut
21
using Test
32
using SpelledOut
43

@@ -61,4 +60,4 @@ end
6160
@test spelled_out(105, lang = :pt) == "cento e cinco"
6261
end
6362

64-
nothing
63+

0 commit comments

Comments
 (0)