From 2499acd58fef9f1f92602d2a2fc4d0389af43566 Mon Sep 17 00:00:00 2001 From: chieveit Date: Tue, 2 Jun 2020 07:34:59 +0000 Subject: [PATCH] fix alphabet indexAt and stringAt --- src/core/alphabet.js | 28 ++++++++++++++------ test/core/alphabet_test.js | 52 ++++++++++---------------------------- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/src/core/alphabet.js b/src/core/alphabet.js index a856ae07..a9a6ad64 100644 --- a/src/core/alphabet.js +++ b/src/core/alphabet.js @@ -12,13 +12,14 @@ const alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', ' export function stringAt(index) { let str = ''; let cindex = index; - while (cindex >= alphabets.length) { + // Each loop iteration converts a base-26 (alphabet length) digit to a + // character [A-Z]. Because it works from least to most significant digit, + // each new character must be added to the FRONT of the string being built. + do { + str = alphabets[parseInt(cindex, 10) % alphabets.length] + str; cindex /= alphabets.length; cindex -= 1; - str += alphabets[parseInt(cindex, 10) % alphabets.length]; - } - const last = index % alphabets.length; - str += alphabets[last]; + } while (cindex >= 0); return str; } @@ -30,12 +31,23 @@ export function stringAt(index) { */ export function indexAt(str) { let ret = 0; - for (let i = 0; i < str.length - 1; i += 1) { + // Each loop iteration converts a digit from a base-26 [A-Z] string to a + // base 10 integer, working from most to least significant base-26 digit. + for (let i = 0; i < str.length; i += 1) { + // Calculate offset from 'A', which has character code 65 const cindex = str.charCodeAt(i) - 65; const exponet = str.length - 1 - i; - ret += (alphabets.length ** exponet) + (alphabets.length * cindex); + // 'A' is interpreted as 0 when in the 0th digit, but as 1 when in any + // other digit. Therefore, we will increment cindex so that [0-25] is + // remapped to [1-26] for all digits, then decrement the result after the + // loop completes by 1 to remap the 0th digit back to [0-25]. + // For example: + // 'AA' will be converted to (base-26) 11 by the loop, + // then decremented to (base-26) 10 before being returned, + // which expressed in base-10 is 26. + ret += (cindex + 1) * (alphabets.length ** exponet); } - ret += str.charCodeAt(str.length - 1) - 65; + ret -= 1; return ret; } diff --git a/test/core/alphabet_test.js b/test/core/alphabet_test.js index bea78b51..597e3790 100644 --- a/test/core/alphabet_test.js +++ b/test/core/alphabet_test.js @@ -11,54 +11,30 @@ import { describe('alphabet', () => { describe('.indexAt()', () => { it('should return 0 when the value is A', () => { - assert.equal(indexAt('A'), 0); + assert.equal(indexAt('A'), 1 * 26 ** 0 - 1); }); - it('should return 25 when the value is Z', () => { - assert.equal(indexAt('Z'), 25); + it('should return 27 when the value is AB', () => { + assert.equal(indexAt('AB'), 1 * 26 ** 1 + 2 * 26 ** 0 - 1); }); - it('should return 26 when the value is AA', () => { - assert.equal(indexAt('AA'), 26); + it('should return 730 when the value is ABC', () => { + assert.equal(indexAt('ABC'), 1 * 26 ** 2 + 2 * 26 ** 1 + 3 * 26 ** 0 - 1); }); - it('should return 52 when the value is BA', () => { - assert.equal(indexAt('BA'), 52); - }); - it('should return 54 when the value is BC', () => { - assert.equal(indexAt('BC'), 54); - }); - it('should return 78 when the value is CA', () => { - assert.equal(indexAt('CA'), 78); - }); - it('should return 26 * 26 when the value is ZA', () => { - assert.equal(indexAt('ZA'), 26 * 26); - }); - it('should return 26 * 26 + 26 when the value is AAA', () => { - assert.equal(indexAt('AAA'), (26 * 26) + 26); + it('should return 19009 when the value is ABCD', () => { + assert.equal(indexAt('ABCD'), 1 * 26 ** 3 + 2 * 26 ** 2 + 3 * 26 ** 1 + 4 * 26 ** 0 - 1); }); }); describe('.stringAt()', () => { it('should return A when the value is 0', () => { - assert.equal(stringAt(0), 'A'); - }); - it('should return Z when the value is 25', () => { - assert.equal(stringAt(25), 'Z'); - }); - it('should return AA when the value is 26', () => { - assert.equal(stringAt(26), 'AA'); - }); - it('should return BC when the value is 54', () => { - assert.equal(stringAt(54), 'BC'); - }); - it('should return CB when the value is 78', () => { - assert.equal(stringAt(78), 'CA'); + assert.equal(stringAt(1 * 26 ** 0 - 1), 'A'); }); - it('should return ZA when the value is 26 * 26', () => { - assert.equal(stringAt(26 * 26), 'ZA'); + it('should return AB when the value is 27', () => { + assert.equal(stringAt(1 * 26 ** 1 + 2 * 26 ** 0 - 1), 'AB'); }); - it('should return Z when the value is 26 * 26 + 1', () => { - assert.equal(stringAt((26 * 26) + 1), 'ZB'); + it('should return ABC when the value is 730', () => { + assert.equal(stringAt(1 * 26 ** 2 + 2 * 26 ** 1 + 3 * 26 ** 0 - 1), 'ABC'); }); - it('should return AAA when the value is 26 * 26 + 26', () => { - assert.equal(stringAt((26 * 26) + 26), 'AAA'); + it('should return ABCD when the value is 19009', () => { + assert.equal(stringAt(1 * 26 ** 3 + 2 * 26 ** 2 + 3 * 26 ** 1 + 4 * 26 ** 0 - 1), 'ABCD'); }); }); describe('.expr2xy()', () => {