Skip to content

Commit

Permalink
Merge pull request #38 from M-Scott-Lassiter/next
Browse files Browse the repository at this point in the history
Add the `deconstruct` function to the class
  • Loading branch information
M-Scott-Lassiter authored May 3, 2022
2 parents c962a3d + 1112efe commit 9eb78bb
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 28 deletions.
75 changes: 54 additions & 21 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@
- [decode][15]
- [Parameters][16]
- [Examples][17]
- [deconstruct][18]
- [Parameters][19]
- [Examples][20]

## AlphanumericEncoder

A class for encoding and decoding base 10 integers to a custom alphanumeric base representation.

### Parameters

- `configOptions` **[object][18]?** Optional object defining initial settings for the class (optional, default `{}`)
- `configOptions` **[object][21]?** Optional object defining initial settings for the class (optional, default `{}`)

- `configOptions.allowLowerCaseDictionary` **[boolean][19]?** Whether or not to allow lower case letters in the dictionary
- `configOptions.dictionary` **[string][20]?** Starting dictionary to use
- `configOptions.allowLowerCaseDictionary` **[boolean][22]?** Whether or not to allow lower case letters in the dictionary
- `configOptions.dictionary` **[string][23]?** Starting dictionary to use

### Examples

Expand Down Expand Up @@ -61,7 +64,7 @@ Returns or sets the current dictionary.

#### Parameters

- `newDictionary` **[string][20]** (If setting) String of unique letters and numbers, in order, for the new dictionary
- `newDictionary` **[string][23]** (If setting) String of unique letters and numbers, in order, for the new dictionary

#### Examples

Expand All @@ -76,19 +79,19 @@ console.log(encoder.dictionary) // 'ABCD'
encoder.dictionary = 'ABCDA' // Throws error because the letter 'A' is repeated
```

- Throws **[RangeError][21]** if setting dictionary to `null`, `undefined` or empty string (i.e. `''`)
- Throws **[RangeError][21]** if `newDictionary` contains a non-alphanumeric character
- Throws **[RangeError][21]** if `newDictionary` has a repeating character
- Throws **[RangeError][24]** if setting dictionary to `null`, `undefined` or empty string (i.e. `''`)
- Throws **[RangeError][24]** if `newDictionary` contains a non-alphanumeric character
- Throws **[RangeError][24]** if `newDictionary` has a repeating character

Returns **[string][20]** (If used as getter) The current dictionary in use
Returns **[string][23]** (If used as getter) The current dictionary in use

### allowLowerCaseDictionary

Returns or sets a boolean value that determines whether the dictionary will allow lower case letters or not.

#### Parameters

- `isAllowed` **[boolean][19]** (If setting). Accept truthy or falsy statements.
- `isAllowed` **[boolean][22]** (If setting). Accept truthy or falsy statements.

#### Examples

Expand All @@ -105,7 +108,7 @@ encoder.dictionary = 'ABCDefg'
console.log(encoder.dictionary) // 'ABCDefg'
```

Returns **[boolean][19]** (If used as getter)
Returns **[boolean][22]** (If used as getter)

### resetDefaultDictionary

Expand All @@ -130,7 +133,7 @@ Takes any number and converts it into a base (dictionary length) letter combo.

#### Parameters

- `integerToEncode` **[number][22]** Base 10 integer. If passed a non-integer number, decimal values are truncated.
- `integerToEncode` **[number][25]** Base 10 integer. If passed a non-integer number, decimal values are truncated.
Passing zero, negative numbers, or non-numbers will return `undefined`.

#### Examples
Expand Down Expand Up @@ -169,17 +172,17 @@ console.log(encoder.encode(null)) // undefined
console.log(encoder.encode(undefined)) // undefined
```

- Throws **[RangeError][21]** if `integerToEncode` exceeds the maximum safe integer for Javascript (`2^53 - 1 = 9007199254740991`).
- Throws **[RangeError][24]** if `integerToEncode` exceeds the maximum safe integer for Javascript (`2^53 - 1 = 9007199254740991`).

Returns **[string][20]** Dictionary encoded value
Returns **[string][23]** Dictionary encoded value

### decode

Takes any string and converts it into a base 10 integer based on the defined dictionary.

#### Parameters

- `stringToDecode` **[string][20]** If passed a non-integer number, decimal values are truncated.
- `stringToDecode` **[string][23]** If passed a non-integer number, decimal values are truncated.
Passing an empty string, `null`, or `undefined` will return `undefined`.

#### Examples
Expand Down Expand Up @@ -207,9 +210,34 @@ console.log(encoder.decode('ADBAC')) // 551
console.log(encoder.decode('ANE')) // undefined
```

- Throws **[RangeError][21]** if the decoded integer exceeds the maximum safe integer for Javascript (`2^53 - 1 = 9007199254740991`).
- Throws **[RangeError][24]** if the decoded integer exceeds the maximum safe integer for Javascript (`2^53 - 1 = 9007199254740991`).

Returns **[number][22]** Positive integer representation. If one of the characters is not present in the dictionary, it will return `undefined`.
Returns **[number][25]** Positive integer representation. If one of the characters is not present in the dictionary, it will return `undefined`.

### deconstruct

Takes any string of letters and numbers and deconstructs it into an array of base 10 integers based on the defined dictionary.

#### Parameters

- `stringToDeconstruct` **([string][23] | [number][25])** A string of letters and numbers (e.g. `'A7'`, `'AC22'`, `'7C10F'`)

#### Examples

```javascript
const encoder = new AlphanumericEncoder()
console.log(encoder.deconstruct('A')) // [1]
console.log(encoder.deconstruct('AC22')) // [29, 22]
console.log(encoder.deconstruct('C3ABC123EFGH456')) // [3, 3, 731, 123, 92126, 456]
console.log(encoder.deconstruct('A1aB2B')) // [1, 1, undefined, 2, 2]
console.log(encoder.deconstruct('7AC!23A1%')) // [7, undefined, 23, 1, 1, undefined]
console.log(encoder.deconstruct('')) // undefined
```

- Throws **[Error][26]** if the dictionary contains a number as this function would be unable to differentiate between where a number and dictionary value.

Returns **[Array][27]<[number][25]>** An array of numbers. Characters not present in the dictionary are treated as letters and return `undefined` for that array value.
Passing an empty string (`''`), `null`, or `undefined` will return `undefined` for the whole function.

[1]: #alphanumericencoder
[2]: #parameters
Expand All @@ -228,8 +256,13 @@ Returns **[number][22]** Positive integer representation. If one of the characte
[15]: #decode
[16]: #parameters-4
[17]: #examples-5
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError
[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[18]: #deconstruct
[19]: #parameters-5
[20]: #examples-6
[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[24]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError
[25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[27]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
## [1.4.0](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/compare/v1.3.0...v1.4.0) (2022-05-02)


### :gift: Feature Changes

* add ability to instantiate the class using an optional config object in the constructor ([8dc230b](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/commit/8dc230b164a2689b49d6a7f8b00f51348da8c3f8)), closes [#35](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/35)

- add ability to instantiate the class using an optional config object in the constructor ([8dc230b](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/commit/8dc230b164a2689b49d6a7f8b00f51348da8c3f8)), closes [#35](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/35)

### :dart: Test Changes

* add tests verifying the optional config object works as expected ([f7f7de6](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/commit/f7f7de6681c4bb3ec1083f346d691d4d36795af3)), closes [#35](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/35)
- add tests verifying the optional config object works as expected ([f7f7de6](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/commit/f7f7de6681c4bb3ec1083f346d691d4d36795af3)), closes [#35](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/35)

## [1.3.0](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/compare/v1.2.0...v1.3.0) (2022-05-01)

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@

### Purpose

`alphanumeric-encoder` is a lightweight library with no dependencies. It can encode an integer into a letter representation, and decode the letter back into a number.
`alphanumeric-encoder` is a lightweight library with no dependencies. It can [encode](/../../blob/main/API.md#encode) an integer into a letter representation, [decode](/../../blob/main/API.md#decode) the letter back into a number, and [deconstruct](/../../blob/main/API.md#deconstruct) combined strings of letters and numbers into an array of decoded numbers.

This is useful for converting letter indexes (used by people) to numbers (used by computers). Examples include:

- Spreadsheet columns (e.g. Microsoft Excel's end column is "XFD" which corresponds to 16384)
- Game boards (e.g. Chess, Battleship) use letters and numbers to identify the grid
- Geographic grid reference systems
- Geodesy grid reference systems (e.g. the [United States National Grid](https://en.wikipedia.org/wiki/United_States_National_Grid))

### Install as an NPM Package

Expand All @@ -76,6 +76,10 @@ console.log(encoder.encode(733)) // 'ABE'
console.log(encoder.decode('A')) // 1
console.log(encoder.decode('AC')) // 29
console.log(encoder.decode('ANE')) // 1045

console.log(encoder.deconstruct('C7')) // [3, 7]
console.log(encoder.deconstruct('AC22')) // [29, 22]
console.log(encoder.deconstruct('C3ABC123EFGH456')) // [3, 3, 731, 123, 92126, 456]
```

---
Expand Down Expand Up @@ -103,7 +107,7 @@ Other Node versions and operating systems might support the library, but the tes

`alphanumeric-encoder` and all other files in this repository are distributed as free and open-source software under the [MIT License](/../../blob/main/LICENSE), © 2022.

Both [contributions](/../../blob/main/CONTRIBUTING.md) and [bug reports](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/new/choose) welcome.
Both [contributions](/../../blob/main/CONTRIBUTING.md) and [bug reports](https://github.com/M-Scott-Lassiter/Alphanumeric-Encoder/issues/new/choose) welcome. See the [change log](/../../blob/main/CHANGELOG.md) for specific details of each release.

Leave a :star2: if you find this project useful!

Expand Down
70 changes: 70 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,76 @@ class AlphanumericEncoder {

return result
}

/**
* Takes any string of letters and numbers and deconstructs it into an array of base 10 integers based on the defined dictionary.
*
* @param {string|number} stringToDeconstruct A string of letters and numbers (e.g. `'A7'`, `'AC22'`, `'7C10F'`)
* @throws {Error} if the dictionary contains a number as this function would be unable to differentiate between where a number and dictionary value.
* @returns {number[]} An array of numbers. Characters not present in the dictionary are treated as letters and return `undefined` for that array value.
* Passing an empty string (`''`), `null`, or `undefined` will return `undefined` for the whole function.
* @example
* const encoder = new AlphanumericEncoder()
* console.log(encoder.deconstruct('A')) // [1]
* console.log(encoder.deconstruct('AC22')) // [29, 22]
* console.log(encoder.deconstruct('C3ABC123EFGH456')) // [3, 3, 731, 123, 92126, 456]
* console.log(encoder.deconstruct('A1aB2B')) // [1, 1, undefined, 2, 2]
* console.log(encoder.deconstruct('7AC!23A1%')) // [7, undefined, 23, 1, 1, undefined]
* console.log(encoder.deconstruct('')) // undefined
*
*/
deconstruct(stringToDeconstruct) {
// The dictionary cannot contain numbers, or else the deconstruct function cannot distinguish where
// one code begins and another ends.
if (this.dictionary.match(/[0-9]/)) {
throw new Error('Cannot deconstruct if the dictionary contains numbers.')
}

// Passing falsy values should return undefined
if (
stringToDeconstruct === null ||
stringToDeconstruct === undefined ||
String(stringToDeconstruct).length === 0
) {
return undefined
}

const safeString = String(stringToDeconstruct) // Force argument to string to process number arguments and prevent slice from throwing an error
const deconstructedArray = []
let character = ''
let componentPart = safeString.slice(0, 1) // Initialize with the first character (which has been guranteed present by above guard functions)

// A helper function to push the final component into the array that gets returned. Numbers get added as is, strings get decoded.
const addDecodedElement = (componentString) => {
if (componentString.match(/[0-9]/)) {
deconstructedArray.push(Number.parseInt(componentString, 10)) // Numbers
} else {
deconstructedArray.push(this.decode(componentString)) // Letters
}
}

// If more than one character in safeString, loop through each subsequent character. Once the next character is not
// the same type as the previous group (i.e. flips from letter to number, or vice versa), add the character group to
// deconstructedArray, reset, and move to the next.
for (let i = 2; i <= safeString.length; i++) {
character = safeString.slice(i - 1, i)

// Parse using a RegExp looking for numbers. The !! converts this to either true/false.
if (!!character.match(/[0-9]/) === !!componentPart.match(/[0-9]/)) {
// Same type, concatenate and keep going
componentPart += character
} else {
// Flipped types, add to array and reset
addDecodedElement(componentPart)
componentPart = character
}
}

// Add the final component part (for single character stringToDeconstruct, this will be the only part)
addDecodedElement(componentPart)

return deconstructedArray
}
}

module.exports = AlphanumericEncoder
45 changes: 45 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,48 @@ describe('Test Decoding', () => {
)
})
})

describe('Test Deconstruction', () => {
setupNewEncoderForTesting()

test.each(['', undefined, null])(
'Trying to deconstruct %p should return "undefined"',
(badArgument) => {
expect(encoder.deconstruct(badArgument)).toBeUndefined()
}
)

test('Expect dictionaries containing numbers to throw an error', () => {
expect(() => {
encoder.dictionary = 'ABC123'
encoder.deconstruct('C3')
}).toThrow(/dictionary contains numbers/)
})

const deconstructionTestValues = [
['A', [1]],
['A1', [1, 1]],
['C7', [3, 7]],
['7C', [7, 3]],
['AE18', [31, 18]],
['18AE', [18, 31]],
['1', [1]],
[1, [1]],
[733, [733]],
[[733], [733]],
[['7C'], [7, 3]],
['7C82AA', [7, 3, 82, 27]],
['C3ABC123EFGH456', [3, 3, 731, 123, 92126, 456]],
['A1aB2B', [1, 1, undefined, 2, 2]],
['7AC!23A1%', [7, undefined, 23, 1, 1, undefined]],
['&', [undefined]]
]
test.each(deconstructionTestValues)(
'Under default dictionary, deconstructing %p should return array %p',
// @ts-ignored
(deconstructArgument, resultArray) => {
// @ts-ignore
expect(encoder.deconstruct(deconstructArgument)).toEqual(resultArray)
}
)
})

0 comments on commit 9eb78bb

Please sign in to comment.