sumEveryOther - Interview question of the week from rendezvous with cassidoo
This was a fun, light interview question this week.
Interview question of the week
This week’s question: Given a number, sum every second digit in that number.
Example:
> sumEveryOther(548915381) > 26 // 4+9+5+8 > sumEveryOther(10) > 0 > sumEveryOther(1010.11) > 1 // 0+0+1
My solution
My first pass at answering this question was to just accomplish the thing quickly and see how well it performs.
#mocha
function sumEveryOther(num) { const str = num.toString().replace(/\D+/, ""); let acc = 0; for (let i = 0; i < str.length; i++) { if (i % 2 === 0) continue; const c = str[i]; acc += parseInt(c, 10); } return acc; } mocha.setup("bdd"); const assert = chai.assert; const expect = chai.expect; const should = chai.should(); describe("Given the examples from the question", () => { const expectations = [ { input: 548915381, output: 26, because: "4, 9, 5, and 8" }, { input: 10, output: 0, because: "0" }, { input: 1010.11, output: 1, because: "0, 0, and 1" }, ]; expectations.forEach(({ input, output, because }) => { it(`should return ${output} for the number ${input} because the expected digits are ${because}`, () => { sumEveryOther(input).should.equal(output); }); }); }); mocha.run();
I figured there might be a faster way than using regular expressions and calling parseInt()
for each qualifying digit, so I took another pass that made use of the UTF-8/ASCII values of the characters in the number string.
#mocha
const ZERO_CHAR_CODE = "0".charCodeAt(0); const NINE_CHAR_CODE = "9".charCodeAt(0); function sumEveryOther(num) { const str = num.toString(); let acc = 0; let skip = true; for (let i = 0; i < str.length; i++) { const c = str.charCodeAt(i); if (c < ZERO_CHAR_CODE || NINE_CHAR_CODE < c) { continue; } if (skip) { skip = false; continue; } skip = true; acc += c - ZERO_CHAR_CODE; } return acc; } mocha.setup("bdd"); const assert = chai.assert; const expect = chai.expect; const should = chai.should(); describe("Given the examples from the question", () => { const expectations = [ { input: 548915381, output: 26, because: "4, 9, 5, and 8" }, { input: 10, output: 0, because: "0" }, { input: 1010.11, output: 1, because: "0, 0, and 1" }, ]; expectations.forEach(({ input, output, because }) => { it(`should return ${output} for the number ${input} because the expected digits are ${because}`, () => { sumEveryOther(input).should.equal(output); }); }); }); mocha.run();
I tested both implementations and included a rudimentary performance test where the function is called 1,000,000 times with random numbers:
#mocha
mocha.setup("bdd"); const assert = chai.assert; const expect = chai.expect; const should = chai.should(); describe("When comparing an optimized implementation to an unoptimized implementation", () => { const maxIterations = 1_000_000; const unoptimized = getUnoptimized(); const optimized = getOptimized(); const results = []; for (const subject of [unoptimized, optimized]) { const start = performance.now(); for (let i = 0; i < maxIterations; i++) { const rand = (Math.random() * 1_000_000_000_000) / 1_000_000; subject(rand); } const end = performance.now(); results.push(end - start); } const [unoptimizedElapsedTimeMs, optimizedElapsedTimeMs] = results; it("should be faster", () => { optimizedElapsedTimeMs.should.be.below(unoptimizedElapsedTimeMs); }); }); mocha.run(); // Glue... function getUnoptimized(){ function sumEveryOther(num) { const str = num.toString().replace(/\D+/, ""); let acc = 0; for (let i = 0; i < str.length; i++) { if (i % 2 === 0) continue; const c = str[i]; acc += parseInt(c, 10); } return acc; } return sumEveryOther;} function getOptimized(){ const ZERO_CHAR_CODE = "0".charCodeAt(0); const NINE_CHAR_CODE = "9".charCodeAt(0); function sumEveryOther(num) { const str = num.toString(); let acc = 0; let skip = true; for (let i = 0; i < str.length; i++) { const c = str.charCodeAt(i); if (c < ZERO_CHAR_CODE || NINE_CHAR_CODE < c) { continue; } if (skip) { skip = false; continue; } skip = true; acc += c - ZERO_CHAR_CODE; } return acc; } return sumEveryOther;}