Skip to content

Commit 291175e

Browse files
committed
perf: optimize countWords function for large payloads
1 parent 220fb10 commit 291175e

2 files changed

Lines changed: 96 additions & 8 deletions

File tree

packages/sdk/src/index.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,66 @@ describe("ReplexicaEngine", () => {
8787
});
8888
});
8989
});
90+
91+
92+
describe("LingoDotDevEngine - countWordsInRecord", () => {
93+
const engine = new LingoDotDevEngine({ apiKey: "test" });
94+
95+
it("should return 0 for falsy values and empty inputs", () => {
96+
expect((engine as any).countWordsInRecord(null)).toBe(0);
97+
expect((engine as any).countWordsInRecord(undefined)).toBe(0);
98+
expect((engine as any).countWordsInRecord("")).toBe(0);
99+
expect((engine as any).countWordsInRecord({})).toBe(0);
100+
expect((engine as any).countWordsInRecord([])).toBe(0);
101+
});
102+
103+
it("should count words in a simple string", () => {
104+
expect((engine as any).countWordsInRecord("Hello world")).toBe(2);
105+
expect((engine as any).countWordsInRecord(" one two three ")).toBe(3);
106+
expect((engine as any).countWordsInRecord("\tNew\nlines and\ttabs\r\n")).toBe(4);
107+
});
108+
109+
it("should count words in an array of strings", () => {
110+
// "a" -> 1, "b c" -> 2, "d" -> 1
111+
expect((engine as any).countWordsInRecord(["a", "b c", "d"])).toBe(1 + 2 + 1);
112+
});
113+
114+
it("should count words in a nested array", () => {
115+
// Here, the function is fully recursive:
116+
// "a" → 1, "b c" → 2, and nested ["d e"] → "d e" → 2 words.
117+
// Total = 1 + 2 + 2 = 5 words.
118+
expect((engine as any).countWordsInRecord(["a", "b c", ["d e"]])).toBe(5);
119+
});
120+
121+
it("should count words in a nested object", () => {
122+
// Object { a: "hello", b: { c: "c d" } }:
123+
// "hello" → 1, and "c d" → 2 words.
124+
// Total = 1 + 2 = 3.
125+
expect((engine as any).countWordsInRecord({ a: "hello", b: { c: "c d" } })).toBe(3);
126+
});
127+
it("should perform efficiently on large payloads", () => {
128+
// Generate a large payload with many key-value pairs (in lakhs)
129+
const largePayload: Record<string, string> = {};
130+
const sampleText = "This is a sample text for benchmarking the countWordsInRecord function";
131+
// Count words in sampleText:
132+
// ["This", "is", "a", "sample", "text", "for", "benchmarking", "the", "countWordsInRecord", "function"]
133+
// That is 10 words.
134+
const repetitions = 100_000; // 1 lakh
135+
for (let i = 0; i < repetitions; i++) {
136+
largePayload[`key_${i}`] = sampleText;
137+
}
138+
const expectedTotalWords = repetitions * 10;
139+
140+
const startTime = performance.now();
141+
const result = (engine as any).countWordsInRecord(largePayload);
142+
const endTime = performance.now();
143+
const elapsed = endTime - startTime;
144+
145+
console.log(`Large payload processed in ${elapsed.toFixed(2)} ms`);
146+
147+
expect(result).toBe(expectedTotalWords);
148+
// 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
150+
});
151+
});
152+

packages/sdk/src/index.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,18 +174,43 @@ export class LingoDotDevEngine {
174174
* @param payload - The payload to count words in
175175
* @returns The total number of words
176176
*/
177+
// private countWordsInRecord(payload: any | Record<string, any> | Array<any>): number {
178+
// if (Array.isArray(payload)) {
179+
// return payload.reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
180+
// } else if (typeof payload === "object" && payload !== null) {
181+
// return Object.values(payload).reduce((acc: number, item) => acc + this.countWordsInRecord(item), 0);
182+
// } else if (typeof payload === "string") {
183+
// return payload.trim().split(/\s+/).filter(Boolean).length;
184+
// } else {
185+
// return 0;
186+
// }
187+
// }
188+
177189
private countWordsInRecord(payload: any | Record<string, any> | Array<any>): number {
178-
if (Array.isArray(payload)) {
179-
return payload.reduce((acc, item) => acc + this.countWordsInRecord(item), 0);
180-
} else if (typeof payload === "object" && payload !== null) {
181-
return Object.values(payload).reduce((acc: number, item) => acc + this.countWordsInRecord(item), 0);
182-
} else if (typeof payload === "string") {
183-
return payload.trim().split(/\s+/).filter(Boolean).length;
184-
} else {
185-
return 0;
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+
}
186207
}
208+
209+
return totalWordCount;
187210
}
188211

212+
213+
189214
/**
190215
* Localize a typical JavaScript object
191216
* @param obj - The object to be localized (strings will be extracted and translated)

0 commit comments

Comments
 (0)