diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b42e01e --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Numbering Patterns + +> Convert numbers to a specified numbering pattern and vice-versa. + +[![NPM](https://img.shields.io/npm/v/numbering-patterns.svg)](https://www.npmjs.com/package/numbering-patterns) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) + +## Install + +```bash +npm install --save numbering-patterns +``` + +## Usage + +```jsx +import {convert, parse, flags} from 'numbering-patterns' + +convert(3); // output: 'C' +convert(3, flags.PATTERN_ROMAN); // output: 'III' +convert('B'); // output: 2 +convert('V', flags.PATTERN_ROMAN); // output: 5 + +parse('A?B'); // output: 'AB' +parse('IIII'); // output: 'III' +``` + +## Syntax + +```jsx +convert(value, pattern = flags.PATTERN_ALPHA); +``` + +Returns a string or an integer depending on what __value__ is passed to it. + +* **value**: The string convert to an integer or the integer to convert to a numbering pattern string. +* **pattern**: The name of the numbering pattern to use for the conversion *(default = flags.PATTERN_ALPHA)*. + +--- + +```jsx +parse(value, pattern = flags.PATTERN_ALPHA); +``` + +Returns a string in the correct format of the specified numbering pattern. + +* **value**: The string to parse to a correctly formatted numbering pattern string. +* **pattern**: The name of the numbering pattern to parse the string to *(default = flags.PATTERN_ALPHA)*. + +## Supported patterns + +The following numbering patterns are supported. + +### Alphabetic numeral + +__Flag name:__ +``` +flags.PATTERN_ALPHA +``` + +__Example:__ +``` +1, 2, 3, ..., 53, 54, 55, ... +A, B, C, ..., AA, AB, AC, ... +``` + +### Roman numeral + +__Flag name:__ +``` +flags.PATTERN_ROMAN +``` + +__Example:__ +``` +1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 50, ..., 100, ..., 500, ... +I, II, III, IV, V, VI, VII, VIII, IX, X, ..., L, ..., C, ..., D, ... +``` + +## License + +MIT © [LSVH](https://github.com/LSVH) diff --git a/index.js b/index.js new file mode 100644 index 0000000..a39cd23 --- /dev/null +++ b/index.js @@ -0,0 +1,15 @@ +const flags = require('./src/flags'); +const patterns = require('./src/patterns'); + +const convert = (value, pattern = flags.PATTERN_ALPHA) => { + if (patterns.hasOwnProperty(pattern)) { + return patterns[pattern](value); + } else { + return value; + } +}; + +const parse = (value, pattern = flags.PATTERN_ALPHA) => + typeof value === 'string' ? convert(convert(value, pattern), pattern) : value; + +module.exports = {flags, patterns, convert, parse}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c9400e3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,249 @@ +{ + "name": "numbering-patterns", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c9e2bcb --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "numbering-patterns", + "version": "1.0.0", + "description": "Convert numbers to a specified numbering pattern and vice-versa.", + "main": "index.js", + "scripts": { + "test": "mocha src/tests/**/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/LSVH/numbering-patterns.git" + }, + "keywords": [ + "library", + "numbers", + "patterns" + ], + "author": "LSVH", + "license": "MIT", + "bugs": { + "url": "https://github.com/LSVH/numbering-patterns/issues" + }, + "homepage": "https://github.com/LSVH/numbering-patterns#readme", + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^5.2.0" + } +} diff --git a/src/flags.js b/src/flags.js new file mode 100644 index 0000000..09d71b0 --- /dev/null +++ b/src/flags.js @@ -0,0 +1,4 @@ +module.exports = { + PATTERN_ALPHA: 'alpha', + PATTERN_ROMAN: 'roman', +} \ No newline at end of file diff --git a/src/patterns.js b/src/patterns.js new file mode 100644 index 0000000..8665e25 --- /dev/null +++ b/src/patterns.js @@ -0,0 +1,8 @@ +const flags = require('./flags'); +const alpha = require('./patterns/alpha'); +const roman = require('./patterns/roman'); + +module.exports = { + [flags.PATTERN_ALPHA]: alpha, + [flags.PATTERN_ROMAN]: roman, +}; \ No newline at end of file diff --git a/src/patterns/alpha.js b/src/patterns/alpha.js new file mode 100644 index 0000000..2d8a741 --- /dev/null +++ b/src/patterns/alpha.js @@ -0,0 +1,53 @@ +module.exports = input => { + const chars = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; + if (typeof input === 'string') { + input = input.toUpperCase() + .split('').filter(x => chars.indexOf(x) !== -1).join(''); + + switch (input.length) { + case 0: + return input; + + case 1: + const indexOf = chars.indexOf(input); + + return indexOf !== -1 ? indexOf + 1 : input; + + default: + const inputLength = input.length; + const lastIndexOf = chars.indexOf(input.charAt(inputLength - 1)); + + let output = lastIndexOf !== -1 ? lastIndexOf + 1 : 0; + + for (let i = 1; i < inputLength; i++) { + const indexOf = chars.indexOf(input.charAt(inputLength - (i + 1))); + + output += indexOf !== -1 ? + (chars.length ** (inputLength - i)) * (indexOf + 1) : 0; + } + + return output; + } + } else { + input = Math.round(input); + + const charsLength = chars.length; + + if (input <= charsLength) { + return chars[input - 1]; + + } else { + const rem = Math.floor(input % charsLength); + const div = Math.floor(input / charsLength); + + const numToStr = (num, str = '') => { + const rem = Math.floor(num % charsLength); + const div = Math.floor(num / charsLength); + + return rem !== 0 ? numToStr(div, str + chars[rem - 1]) : str; + }; + + return numToStr(div) + chars[rem -1]; + } + } +}; \ No newline at end of file diff --git a/src/patterns/roman.js b/src/patterns/roman.js new file mode 100644 index 0000000..a5bf427 --- /dev/null +++ b/src/patterns/roman.js @@ -0,0 +1,92 @@ +module.exports = input => { + const halves = ['V','L','D']; + const wholes = ['I','X','C','M']; + if (typeof input === 'string') { + let steps = []; + for (let i = 0; i < input.length; i++) { + const char = input.charAt(i); + const stepsLength = steps.length; + const prevChar = stepsLength ? steps[stepsLength - 1].charAt(0) : ''; + const prevCharLength = stepsLength ? steps[stepsLength - 1].length : 0; + + if (stepsLength && prevCharLength < 3 && prevChar === char) { + steps[stepsLength - 1] += char; + + } else { + const charAsWhole = wholes.indexOf(char); + const charAsHalve = halves.indexOf(char); + + const checkPrevChar = thePrevChar => thePrevChar !== -1 && ( + // Lower chars's can be appended to higher prevChar's. + // e.g. 'XI' or 'XV' but not 'IX'. + ( + thePrevChar > charAsWhole && + thePrevChar > charAsHalve + ) + || + // One level higher prevChar's can be prepended to + // lower char's. But only if there is only one prevChar. + // e.g. 'IX' or 'IV' but not 'IIX', 'IC' or 'IV'. + ( + ( + thePrevChar >= charAsWhole - 1 && + thePrevChar >= charAsHalve - 1 + ) && + prevCharLength === 1 + ) + ); + + if ((stepsLength && ( + checkPrevChar(wholes.indexOf(prevChar)) || + checkPrevChar(halves.indexOf(prevChar)) + )) || (!stepsLength && (charAsWhole || charAsHalve))) { + steps.push(char); + } else { + continue; + } + } + } + + let out = 0; + let prevValue = 0; + for (let i = 0; i < steps.length; i++) { + const char = steps[i].charAt(0); + + let value = 0; + if (halves.indexOf(char) !== -1) { + value = (10 ** halves.indexOf(char)) * 5; + if (value > prevValue) { + value = value - (prevValue * 2); + } + } else { + if (wholes.indexOf(char) === -1) continue; + + // Characters what represent whole numbers can't be longer then 3 characters. + const length = steps[i].length <= 3 ? steps[i].length : 3; + + value = (10 ** wholes.indexOf(char)) * length; + } + + prevValue = value; + out += value; + } + return out; + } else { + input = input.toString(); + + let output = ''; + for (let i = 0; i < input.length; i++) { + const interval = parseInt(input.charAt(input.length - (i + 1))); + + output = (interval > 0 && interval < 4 ? wholes[i].repeat(interval) : ( + interval === 4 ? wholes[i] + halves[i] : ( + interval === 5 ? halves[i] : ( + interval > 5 && interval < 9 ? + halves[i] + wholes[i].repeat(interval - 5) : ( + interval === 9 ? wholes[i] + wholes[i + 1] : '' + ))))) + output; + } + + return output; + } +}; \ No newline at end of file diff --git a/src/tests/index.js b/src/tests/index.js new file mode 100644 index 0000000..56225ce --- /dev/null +++ b/src/tests/index.js @@ -0,0 +1,59 @@ +const expect = require('chai').expect; +const convert = require('../../').convert; +const parse = require('../../').parse; +const flags = require('../../').flags; + +describe('convert(value [, flags.PATTERN_ALPHA])', () => { + const string = 'ABC'; + const invalidString = 'A?B?C!'; + const number = ( + 3 + // AB[C] + ((26 ** 2) * 2) + // A[B]C + ((26 ** 1) * 1) // [A]BC + ); + + it('set value to a string ('+string+'), expect a number ('+number+').', + () => expect(convert(string)).to.deep.equal(number)); + + it('set value to a number ('+number+'), expect a string ('+string+').', + () => expect(convert(number)).to.deep.equal(string)); + + it('set value to a incorrectly formatted string ('+invalidString+'),' + +' expect a correctly formatted string ('+string+').', + () => expect(parse(invalidString)).to.deep.equal(string)); +}); + +describe('convert(value, flags.PATTERN_ROMAN)', () => { + const pattern = flags.PATTERN_ROMAN; + const string = 'MCCXLVI'; + const invalidString = 'M?C?C?X?L?V?I'; + const number = ( + 1000 + // [M]CCXLVI + 200 + // M[CC]XLVI + 40 + // MCC[XL]VI + 6 // MCCXL[VI] + ); + + it('set value to a string ('+string+'), expect a number ('+number+').', + () => expect(convert(string, pattern)).to.deep.equal(number)); + + it('set value to a number ('+number+'), expect a string ('+string+').', + () => expect(convert(number, pattern)).to.deep.equal(string)); +}); + +describe('parse(value [, flags.PATTERN_ALPHA])', () => { + const expected = 'ABC'; + const input = 'A?B?C!'; + it('parse incorrect string ('+input+'), to a correct string ('+expected+').', + () => expect(parse(input)).to.deep.equal(expected)); + +}); + +describe('parse(value, flags.PATTERN_ROMAN)', () => { + const pattern = flags.PATTERN_ROMAN; + const expected = 'MCCXLVI'; + const input = 'M?C?C?X?L?V?I'; + it('parse incorrect string ('+input+'), to a correct string ('+expected+').', + () => expect(parse(input)).to.deep.equal(expected), pattern); + +}); \ No newline at end of file