Skip to content

Commit 46192c9

Browse files
committed
perf(countWords): optimize performance using iterative DFS
1 parent 5f05d26 commit 46192c9

2 files changed

Lines changed: 53 additions & 22 deletions

File tree

packages/sdk/src/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ describe("LingoDotDevEngine - countWordsInRecord", () => {
146146

147147
expect(result).toBe(expectedTotalWords);
148148
// Optionally, assert that the function finishes within a reasonable time threshold:
149-
expect(elapsed).toBeLessThan(5000); // e.g., under 5 seconds for 1 lakh entries
149+
expect(elapsed).toBeLessThan(5000);
150150
});
151151
});
152152

packages/sdk/src/index.ts

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export class LingoDotDevEngine {
174174
* @param payload - The payload to count words in
175175
* @returns The total number of words
176176
*/
177+
177178
// private countWordsInRecord(payload: any | Record<string, any> | Array<any>): number {
178179
// if (Array.isArray(payload)) {
179180
// return payload.reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
@@ -186,31 +187,61 @@ export class LingoDotDevEngine {
186187
// }
187188
// }
188189

189-
private countWordsInRecord(payload: any | Record<string, any> | Array<any>): number {
190-
const stack: any[] = [payload];
191-
let totalWordCount = 0;
192-
193-
while (stack.length > 0) {
194-
const current = stack.pop();
195-
196-
if (current === null || current === undefined) {
197-
continue;
198-
}
199-
200-
if (Array.isArray(current)) {
201-
stack.push(...current);
202-
} else if (typeof current === 'object') {
203-
stack.push(...Object.values(current));
204-
} else if (typeof current === 'string') {
205-
totalWordCount += current.trim().split(/\s+/).filter(Boolean).length;
206-
}
190+
private countWordsInRecord(payload: unknown): number {
191+
let wordCount = 0;
192+
const processingStack: unknown[] = [payload];
193+
const SPACE_CHAR_CODE = 32;
194+
const TAB_CHAR_CODE = 9;
195+
const NEWLINE_CHAR_CODE = 10;
196+
197+
while (processingStack.length > 0) {
198+
const currentItem = processingStack.pop();
199+
200+
if (typeof currentItem === 'string') {
201+
let isBetweenWords = true;
202+
let currentWordCount = 0;
203+
204+
for (let i = 0; i < currentItem.length; i++) {
205+
const charCode = currentItem.charCodeAt(i);
206+
const isWhitespace = charCode === SPACE_CHAR_CODE ||
207+
charCode === TAB_CHAR_CODE ||
208+
charCode === NEWLINE_CHAR_CODE;
209+
210+
if (isBetweenWords && !isWhitespace) {
211+
currentWordCount++;
212+
isBetweenWords = false;
213+
} else if (!isBetweenWords && isWhitespace) {
214+
isBetweenWords = true;
215+
}
216+
}
217+
218+
wordCount += currentWordCount;
219+
}
220+
else if (Array.isArray(currentItem)) {
221+
// Process array elements in reverse to maintain original order
222+
for (let i = currentItem.length - 1; i >= 0; i--) {
223+
processingStack.push(currentItem[i]);
224+
}
225+
}
226+
else if (this.isRecord(currentItem)) {
227+
// Process object properties efficiently
228+
for (const key in currentItem) {
229+
if (Object.prototype.hasOwnProperty.call(currentItem, key)) {
230+
processingStack.push(currentItem[key]);
231+
}
232+
}
233+
}
207234
}
208-
209-
return totalWordCount;
210-
}
211235

236+
return wordCount;
237+
}
212238

239+
// Type guard for plain objects
240+
private isRecord(value: unknown): value is Record<string, unknown> {
241+
return value !== null && typeof value === 'object' && !Array.isArray(value);
242+
}
213243

244+
214245
/**
215246
* Localize a typical JavaScript object
216247
* @param obj - The object to be localized (strings will be extracted and translated)

0 commit comments

Comments
 (0)