Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix alphabet indexAt and stringAt #465

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions src/core/alphabet.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
52 changes: 14 additions & 38 deletions test/core/alphabet_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()', () => {
Expand Down