Skip to content

Commit cc71388

Browse files
committed
Reduce mem alloc, after pprof
1 parent 451bafc commit cc71388

File tree

3 files changed

+137
-112
lines changed

3 files changed

+137
-112
lines changed

aznum2words.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,28 +88,32 @@ func SpellNumber(numberAsStr string) (string, error) {
8888
// handleIntegerNumberConversion("-79594") -> "mənfi yetmiş doqquz min beş yüz doxsan dörd"
8989
// handleIntegerNumberConversion("81") -> "səksən bir"
9090
func handleIntegerNumberConversion(intValueAsStr string) (string, error) {
91-
wordBuilder := make([]string, 0)
91+
var builder strings.Builder
9292

9393
signKeyword, err := getSignSymbolAsWord(intValueAsStr)
9494
if err != nil {
9595
return "", err
9696
}
9797

9898
if signKeyword != "" {
99-
wordBuilder = append(wordBuilder, signKeyword)
99+
builder.WriteString(signKeyword)
100+
builder.WriteString(" ")
100101
}
101102

102103
intValueWithoutSign := removeSignMarkIfExists(intValueAsStr)
103-
spelledInteger := convertIntPart(intValueWithoutSign)
104+
spelledInteger, err := convertIntPart(intValueWithoutSign)
105+
if err != nil {
106+
return "", err
107+
}
104108

105109
// handling case: intValueAsStr is '-0'
106110
if intValueWithoutSign == "0" {
107111
return spelledInteger, nil
108112
}
109113

110-
wordBuilder = append(wordBuilder, spelledInteger)
114+
builder.WriteString(spelledInteger)
111115

112-
return strings.Join(wordBuilder, " "), nil
116+
return builder.String(), nil
113117
}
114118

115119
// The function intended to handle the conversion of floating point numbers into words
@@ -124,7 +128,10 @@ func handleFloatingPointNumberConversion(floatValueAsStr string) (string, error)
124128
floatValueAsStr = removeSignMarkIfExists(floatValueAsStr)
125129
slices := strings.Split(floatValueAsStr, DecimalPointSeparator)
126130

127-
intPartAsWord := convertIntPart(slices[0])
131+
intPartAsWord, err := convertIntPart(slices[0])
132+
if err != nil {
133+
return "", err
134+
}
128135

129136
floatingPart := slices[1]
130137

@@ -139,7 +146,11 @@ func handleFloatingPointNumberConversion(floatValueAsStr string) (string, error)
139146
var floatingPartAsIntegerWithWord string
140147
var suffix string
141148
if len(floatingPart) != 0 {
142-
floatingPartAsIntegerWithWord = convertIntPart(floatingPart)
149+
floatingPartAsIntegerWithWord, err = convertIntPart(floatingPart)
150+
if err != nil {
151+
return "", err
152+
}
153+
143154
cnt := len(floatingPart)
144155
separatorKey := int(math.Pow10(cnt))
145156
resultSuffix, ok := floatingPointDict[separatorKey]

converters.go

Lines changed: 79 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -90,38 +90,56 @@ func getKeyword(position int) (keyword string, err error) {
9090
}
9191

9292
// The function intended to convert any sized positive integer numbers into words
93-
func convertIntPart(strArg string) string {
93+
func convertIntPart(strArg string) (string, error) {
9494
str := removeSignMarkIfExists(strArg)
95+
length := len(str)
9596

96-
//used to indicate level 10^3, 10^6
97-
pos := 1
98-
wordBuilder := make([]string, 0)
97+
if length == 0 {
98+
return "", nil
99+
}
99100

100-
//starting from right hand side, pick up triples and start process it
101-
for i := len(str); i >= 0; i = i - 3 {
101+
// Calculate how many triples will be processed
102+
tripleCount := (length + 2) / 3
103+
words := make([]string, tripleCount*2) // Worst case: each triple + a keyword
104+
wordIndex := tripleCount*2 - 1 // Fill from the end to avoid reversing
105+
106+
pos := 1
107+
for i := length; i > 0; i -= 3 {
102108
key, _ := getKeyword(pos)
103109
pos++
104110

105-
var leftPointer int = i - 3
111+
leftPointer := i - 3
106112
if leftPointer < 0 {
107113
leftPointer = 0
108114
}
109-
var rightPointer int = i
110115

111-
tripleAsWords, _ := tripleToWord(str[leftPointer:rightPointer])
116+
tripleAsWords, err := tripleToWord(str[leftPointer:i])
117+
if err != nil {
118+
return "", err
119+
}
112120

113-
//insert triple `tripleAsWords` in the front of result
114-
if len(tripleAsWords) != 0 {
115-
if len(key) != 0 {
116-
wordBuilder = append([]string{tripleAsWords, key}, wordBuilder...)
117-
} else {
118-
wordBuilder = append([]string{tripleAsWords}, wordBuilder...)
121+
if tripleAsWords != "" {
122+
if key != "" {
123+
words[wordIndex] = key
124+
wordIndex--
119125
}
126+
words[wordIndex] = tripleAsWords
127+
wordIndex--
120128
}
129+
}
121130

131+
// Construct the final string using strings.Builder
132+
var builder strings.Builder
133+
for i := wordIndex + 1; i < len(words); i++ {
134+
if words[i] != "" {
135+
if builder.Len() > 0 {
136+
builder.WriteByte(' ')
137+
}
138+
builder.WriteString(words[i])
139+
}
122140
}
123141

124-
return strings.Join(wordBuilder, " ")
142+
return builder.String(), nil
125143
}
126144

127145
// convertOneDigitIntoWord("0") -> "sıfır"
@@ -145,38 +163,28 @@ func convertTwoDigitsIntoWord(twoDigitsWord string) (string, error) {
145163
return "", ErrInvalidArgument
146164
}
147165

148-
var textBuilder []string
149-
var idxAt1 int
150-
if i, err := strconv.Atoi(twoDigitsWord[1:]); err != nil {
151-
return "", err
152-
} else {
153-
idxAt1 = i
154-
}
155-
var wordForIdxAt1 string
156-
if idxAt1 == 0 {
157-
wordForIdxAt1 = ""
158-
} else {
159-
wordForIdxAt1 = digits[idxAt1]
160-
}
161-
162-
if len(wordForIdxAt1) != 0 {
163-
textBuilder = append([]string{wordForIdxAt1}, textBuilder...)
164-
}
166+
var builder strings.Builder
167+
first := true // To manage spacing between words
165168

166-
var idxAt0 int
167-
if i, err := strconv.Atoi(twoDigitsWord[0:1]); err != nil {
169+
// Process tens place (index 0)
170+
if val, err := strconv.Atoi(twoDigitsWord[0:1]); err != nil {
168171
return "", err
169-
} else {
170-
idxAt0 = i
172+
} else if val != 0 {
173+
builder.WriteString(tens[val])
174+
first = false
171175
}
172176

173-
wordForIdxAt0 := tens[idxAt0]
174-
175-
if len(wordForIdxAt0) != 0 {
176-
textBuilder = append([]string{wordForIdxAt0}, textBuilder...)
177+
// Process ones place (index 1)
178+
if val, err := strconv.Atoi(twoDigitsWord[1:]); err != nil {
179+
return "", err
180+
} else if val != 0 {
181+
if !first {
182+
builder.WriteString(" ")
183+
}
184+
builder.WriteString(digits[val])
177185
}
178186

179-
return strings.Join(textBuilder, " "), nil
187+
return builder.String(), nil
180188
}
181189

182190
// convertThreeDigitsIntoWord("390") -> "üç yüz doxsan"
@@ -186,65 +194,42 @@ func convertThreeDigitsIntoWord(threeDigitsWord string) (string, error) {
186194
return "", ErrInvalidArgument
187195
}
188196

189-
var textBuilder []string
190-
for i := len(threeDigitsWord) - 1; i >= 0; i-- {
191-
192-
var word string
193-
switch i {
194-
case 2:
195-
var ValAtIdx2 int
196-
if i, err := strconv.Atoi(threeDigitsWord[2:]); err != nil {
197-
return "", err
198-
} else {
199-
ValAtIdx2 = i
200-
}
201-
if ValAtIdx2 != 0 {
202-
word = digits[ValAtIdx2]
203-
}
204-
205-
case 1:
206-
var ValAtIdx1 int
207-
if i, err := strconv.Atoi(threeDigitsWord[1:2]); err != nil {
208-
return "", err
209-
} else {
210-
ValAtIdx1 = i
211-
}
212-
if ValAtIdx1 != 0 {
213-
word = tens[ValAtIdx1]
214-
} else {
215-
word = ""
216-
}
217-
case 0:
218-
var ValAtIdx0 int
219-
if i, err := strconv.Atoi(threeDigitsWord[0:1]); err != nil {
220-
return "", err
221-
} else {
222-
ValAtIdx0 = i
223-
}
224-
isNonZero := ValAtIdx0 != 0
197+
// Use a strings.Builder to avoid heap allocations during string construction
198+
var builder strings.Builder
199+
first := true // To manage spacing between words
225200

226-
var prefix string
227-
if isNonZero {
228-
prefix = digits[ValAtIdx0]
229-
} else {
230-
prefix = ""
231-
}
201+
// Process hundreds place
202+
if val, err := strconv.Atoi(threeDigitsWord[0:1]); err != nil {
203+
return "", err
204+
} else if val != 0 {
205+
builder.WriteString(digits[val])
206+
builder.WriteString(" ")
207+
builder.WriteString(HundredAsString)
208+
first = false
209+
}
232210

233-
if len(prefix) > 0 {
234-
word = prefix + " " + HundredAsString
235-
} else {
236-
word = ""
237-
}
238-
default:
239-
return "", ErrUnexpectedBehaviour
211+
// Process tens place
212+
if val, err := strconv.Atoi(threeDigitsWord[1:2]); err != nil {
213+
return "", err
214+
} else if val != 0 {
215+
if !first {
216+
builder.WriteString(" ")
240217
}
218+
builder.WriteString(tens[val])
219+
first = false
220+
}
241221

242-
if len(word) != 0 {
243-
textBuilder = append([]string{word}, textBuilder...)
222+
// Process ones place
223+
if val, err := strconv.Atoi(threeDigitsWord[2:]); err != nil {
224+
return "", err
225+
} else if val != 0 {
226+
if !first {
227+
builder.WriteString(" ")
244228
}
229+
builder.WriteString(digits[val])
245230
}
246231

247-
return strings.Join(textBuilder, " "), nil
232+
return builder.String(), nil
248233
}
249234

250235
// The function intended to convert triples("1", "12", "123") into words' representation

0 commit comments

Comments
 (0)