1. Speed
As to the speed of the algorithm, there are the following considerations:
- About half of the loop's iterations will lead to no output. This could be improved;
- There is a pattern that repeats every 15 iterations, because the least common multiple of 3 and 5 is 15.
So you could hardcode that pattern into your code, and perform the output corresponding to the range of 1..15, and then repeat:
function counterPattern() {
for (let i = 0; i < 90; i += 15) {
console.log("a"); // 3 + i
console.log("b"); // 5 + i
console.log("a"); // 6 + i
console.log("a"); // 9 + i
console.log("b"); // 10 + i
console.log("a"); // 12 + i
console.log("c"); // 15 + i
}
// The remainder: values between 90 and 100:
console.log("a"); // 93
console.log("b"); // 95
console.log("a"); // 96
console.log("a"); // 99
console.log("b"); // 100
}
counterPattern();
2. Testing
To make this code testable, you have several options. Using an array, like you did, is one of them. You could also consider turning the function into a generator, and then use yield
to output the values.
But there is also a solution where you don't have to touch the function's code: Either use mocking or spying to tap into console.log
. Unit testing libraries usually offer such features.
Secondly, there is no better reference to test against than the original function. So you could first run the original function to collect the expected output, and then run the improved implementation. Finally, the two outputs should be compared.
Here is how mocking would work without the use of an external library:
function test() { // Wrapper to keep the mocking local
function orig_counterPattern() { // The reference implementation
for (let i = 1; i <= 100; i++) {
if (i % 3 === 0 && i % 5 === 0) {
console.log("c");
} else if (i % 3 === 0) {
console.log("a");
} else if (i % 5 === 0) {
console.log("b");
}
}
}
function counterPattern() { // Our own implementation
for (let i = 0; i < 90; i += 15) {
console.log("a"); // 3 + i
console.log("b"); // 5 + i
console.log("a"); // 6 + i
console.log("a"); // 9 + i
console.log("b"); // 10 + i
console.log("a"); // 12 + i
console.log("c"); // 15 + i
}
// The remainder: values between 90 and 100:
console.log("a"); // 93
console.log("b"); // 95
console.log("a"); // 96
console.log("a"); // 99
console.log("b"); // 100
}
// Mock console object:
let console = {
log: (value) => output.push(value)
};
// Collect the expected output
let output = [];
orig_counterPattern();
let reference = [...output];
// Run our own implementation
output = [];
counterPattern();
let result = [...output];
// Stop mocking the console object
console = globalThis.console;
// Compare
console.assert(output.length === reference.length, "incorrect number of outputs");
console.assert(reference.every((ref, i) => ref === result[i], "mismatch"));
}
test();
console.log("Test completed.");
Shorter code
First, you could of course hardcode the output completely, even avoiding the loop. But that would go against the principle that we should not repeat ourselves (DRY).
Here is a variant that is a bit more DRY:
function counterPattern() {
let pattern = "abaabac".repeat(105/15).slice(0, -2);
for (let c of pattern) console.log(c);
}
counterPattern();