diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..ec3c615 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + env: { + browser: true, + es6: true, + mocha: true, + }, + extends: [ + 'airbnb-base', + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + }, + rules: { + "no-param-reassign": [0], + "no-underscore-dangle": [0], + "no-unused-expressions": [0], + "no-nested-ternary": [0], + "class-methods-use-this": [0] + }, +}; diff --git a/README.md b/README.md index 4f1eb6a..ba2cc32 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,81 @@ # zip-numbers -Алгоритмы сжатия списков чисел для передачи в тестовом виде. В реализацию входят парсер строк и поле для django-rest-framework. +Алгоритмы сжатия списков чисел для передачи в текстовом виде. В реализацию входят парсер строк и поле для django-rest-framework. + +Поддерживаются 2 способа сжатия: диапазоны с выносом за скобку и дельта-строки. + +## Информация + +1. Сжимает строку по одному из двух способов сжатия, который можно выбрать: + 1. диапазоны с выносом за скобку + 2. дельта-строки + +2. Разжимает строку, сжатую по одному из двух алгоритмов. + +## Установка + +``` +$ npm install zip-numbers +``` + + +## Примеры +#### Encode: +```js +const zip = require('zip-numbers'); + +zip.encode([1,3,6]); +//=> '1(0,2,5)' + +zip.encode([1,3,6], zip.constants.MODE_SIMPLE_STRING); +//=> '1(0,2,5)' + +zip.encode([1,3,6], zip.constants.MODE_DELTA_STRING); +//=> '~.123' +``` +#### Decode: +```js +const zip = require('zip-numbers'); + +zip.decode('1(0,2,5)'); +//=> [1, 3, 6] + +zip.decode('~.123'); +//=> [1, 3, 6] +``` + +## Functions + +
+
encode(tokens, [mode])string
+

Encodes an array of tokens into a string.

+
+
decode(string)Array.<number>
+

Decodes a string into an array of tokens.

+
+
+ + + +## encode(tokens, [mode]) ⇒ string +Encodes an array of tokens into a string. + +**Returns**: string - Encoded string. + +| Param | Type | Default | Description | +| ------ | --------------------------------- | ------------------------------- | ------------------------------------------------------------------- | +| tokens | Array.<number> | | Array of tokens. | +| [mode] | number | MODE_SIMPLE_STRING | Mode: MODE_SIMPLE_STRING or MODE_DELTA_STRING. See: `zip.constants` | + + + +## decode(string) ⇒ Array.<number> +Decodes a string into an array of tokens. + +**Returns**: Array.<number> - Array of tokens. + +| Param | Type | Description | +| ------ | ------------------- | --------------- | +| string | string | Encoded string. | + -Поддерживаются 2 способа сжатия: диапазоны с выносом за скобку и дельта-строки. \ No newline at end of file diff --git a/constants/index.js b/constants/index.js new file mode 100644 index 0000000..87178c8 --- /dev/null +++ b/constants/index.js @@ -0,0 +1,21 @@ +const DELTA = '~'; +const MULTIPLICATION = 'x'; +const NUM_DELIMITER = ','; +const DELTA_LIST_BY_ONE = '.'; +const DELTA_LIST_BY_TWO = ':'; +const ZIP_START_DELIMITER = '('; +const ZIP_END_DELIMITER = ')'; +const MODE_SIMPLE_STRING = 1; +const MODE_DELTA_STRING = 2; + +module.exports = { + DELTA, + MULTIPLICATION, + NUM_DELIMITER, + DELTA_LIST_BY_ONE, + DELTA_LIST_BY_TWO, + ZIP_START_DELIMITER, + ZIP_END_DELIMITER, + MODE_SIMPLE_STRING, + MODE_DELTA_STRING, +}; diff --git a/index.js b/index.js index 9e89856..11fb74e 100644 --- a/index.js +++ b/index.js @@ -1,228 +1,23 @@ - -const NUM_DELIMITER = ','; -const DELTA_LIST_BY_ONE = '.'; -const DELTA_LIST_BY_TWO = ':'; -const ZIP_START_DELIMITER = '('; -const ZIP_END_DELIMITER = ')'; - +const Encode = require('./scripts/encode'); +const Decode = require('./scripts/decode'); +const constants = require('./constants'); /** - * Parse string to array of encoding numbers - * - * @param string - * @returns {[]|*[]} + * Encodes an array of tokens into a string. + * @param {number[]} tokens Array of tokens. + * @param {number} [mode=MODE_SIMPLE_STRING] Mode: MODE_SIMPLE_STRING or MODE_DELTA_STRING. + * See: `zip.constants` + * @returns {string} Encoded string. */ -const decode = string => { - if (!string) { - return []; - } - let items; - - // Parse base for int - if (string.startsWith('x')) { - // Дописать функционал - } - - // Parse empty string as empty list - if (string.startsWith('~')) { - items = parseDelta(string.slice(1)); - console.log(items); - } - else { - items = parseString(string); - console.log(items) - } - - return items; -}; +const encode = (tokens, mode) => new Encode().parse(tokens, mode); /** - * Parse string to tokens - * - * @param string - * @return {[]} + * Decodes a string into an array of tokens. + * @param {string} string Encoded string. + * @returns {number[]} Array of tokens. */ -const parseString = string => { - let buff = '', tokens = [], zipBuff = []; - - for (const ltr of string) { - if (ltr === ZIP_START_DELIMITER) zipBuff.push(1); - if (ltr === ZIP_END_DELIMITER) zipBuff.pop(); - if (zipBuff.length === 0 && ltr === NUM_DELIMITER) { - parseToken(buff).forEach((item) => { - tokens.push(item); - }); - buff = ''; - } - else buff += ltr; - } - - if (buff) { - parseToken(buff).forEach((item) => { - tokens.push(item); - }); - } - - return tokens; -}; - - -/** - * Parse token from string - * - * @param token - * @return {[]} array with tokens - */ -const parseToken = token => { - let tokens = []; - if (token.indexOf(ZIP_START_DELIMITER) > -1) { - let [base, subString] = token.split(ZIP_START_DELIMITER); - base = parseInt(base, 10); - let items = parseString(subString.slice(0, subString.length-1)); - items.forEach((item) => tokens.push(item+base)); - return tokens; - } - if (token.indexOf('-') > -1) { - let [start, stop] = token.split('-'); - start = parseInt(start, 10); - stop = parseInt(stop, 10); - - for (let i = start; i <= stop; i += 1) { - tokens.push(i); - } - } - else tokens = [parseInt(token)]; - return tokens; -}; - -/** - * Parse string by delta - * - * @param string - * @return {[]} array with tokens - */ -const parseDelta = string => { - let tokens = [], - chunks = deltaChunks(string); - - chunks.forEach((chunk) => tokens = tokens.concat(parseDeltaChunks(chunk))); - - let last = 0; - tokens.forEach((token, i) => { - tokens[i]=token+last; - last=tokens[i]; - }); - - return tokens; -}; - - -/** - * Parse chunk of delta - * - * @param chunk - * @return {[]} array with tokens - */ -const parseDeltaChunks = chunk => { - let listBy, blocks, tokens = []; - if (chunk.startsWith(DELTA_LIST_BY_ONE)) listBy = 1; - if (chunk.startsWith(DELTA_LIST_BY_TWO)) listBy = 2; - if (listBy) chunk = chunk.slice(1); - blocks = chunk.split('x'); - - if (listBy) { - if (blocks.length === 1) { - tokens = wrap(chunk, listBy); - } - else { - // Дописать функционал - } - - } - else if (blocks.length === 2) { - let num = parseInt(blocks[1], 10); - for (let i = 0; i < blocks[0]; i++) { - tokens.push(num); - } - } - else tokens = [parseInt(chunk, 10)]; - - return tokens; - - }; - -/** - * Yield chunks for delta string - * - * @param string for split into chunks - * @return [] of chunks - */ -const deltaChunks = string => { - let chunks = [], - buf = ''; - for (let ltr of string) { - if (ltr === NUM_DELIMITER) { - (buf!=='') && chunks.push(buf); - buf = ''; - } - else if (ltr === DELTA_LIST_BY_ONE || ltr === DELTA_LIST_BY_TWO) { - (buf!=='') && chunks.push(buf); - buf = ltr; - } - else { - buf += ltr; - } - } - if (buf !== '') chunks.push(buf); - return chunks; -}; - -/** - * Convert string to several strings of length of count symbols - * - * @param string - * @param count symbols - * @returns {[]} list of several strings - */ -const wrap = (string, count) => { - let list = []; - for (let i = 0; i new Decode().parse(string); +exports.encode = encode; +exports.decode = decode; +exports.constants = constants; diff --git a/package.json b/package.json index 3c1a76f..8ce05e6 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,25 @@ { "name": "zip-numbers", - "version": "1.0.0", + "version": "1.0.3", "description": "Compression writing numbers", - "dependencies": { + "license": "MIT", + "dependencies": {}, + "repository": "git://github.com/jetstyle/js-zip-numbers.git", + "scripts": { + "eslint": "eslint .", + "test": "mocha" + }, + "devDependencies": { + "eslint": "^6.7.2", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-plugin-import": "^2.19.1", "mocha": "^6.2.2" }, - "repository": "git://github.com/jetstyle/js-zip-numbers.git" + "keywords": [ + "numbers", + "encode", + "encodenumbers", + "decode", + "compression" + ] } diff --git a/scripts/decode.js b/scripts/decode.js index c188ac2..1c27dc9 100644 --- a/scripts/decode.js +++ b/scripts/decode.js @@ -1,227 +1,229 @@ -const NUM_DELIMITER = ','; -const DELTA_LIST_BY_ONE = '.'; -const DELTA_LIST_BY_TWO = ':'; -const ZIP_START_DELIMITER = '('; -const ZIP_END_DELIMITER = ')'; - - -/** - * Parse string to array of encoding numbers - * - * @param string - * @returns {[]|*[]} - */ -const decode = string => { +const constants = require('../constants/index.js'); + +class Decode { + /** + * @param maxLength + * @return {number[]} + */ + constructor(maxLength = 1000000) { + this.intBase = 10; + this.maxLength = maxLength; + this.countTokens = 0; + } + + /** + * Parse string to array of encoding numbers + * + * @param string + * @returns {number[]} + */ + parse(string) { if (!string) { - return []; + return []; } + this.countTokens = 0; let items; // Parse base for int - if (string.startsWith('x')) { - // Дописать функционал + if (string.startsWith(constants.MULTIPLICATION)) { + let base; + [base, string] = string.split(';'); + this.intBase = parseInt(base.slice(1), 10); + } else { + this.intBase = 10; } // Parse empty string as empty list - if (string.startsWith('~')) { - items = parseDelta(string.slice(1)); - console.log(items); - } - else { - items = parseString(string); - console.log(items) + if (string.startsWith(constants.DELTA)) { + items = this._parseDelta(string.slice(1)); + } else { + items = this._parseString(string); } return items; -}; - -/** - * Parse string to tokens - * - * @param string - * @return {[]} - */ -const parseString = string => { - let buff = '', tokens = [], zipBuff = []; - + } + + /** + * Parse string to tokens + * + * @param string + * @return {number[]} + * @private + */ + _parseString(string) { + let buff = ''; const tokens = []; const + zipBuff = []; + + // eslint-disable-next-line no-restricted-syntax for (const ltr of string) { - if (ltr === ZIP_START_DELIMITER) zipBuff.push(1); - if (ltr === ZIP_END_DELIMITER) zipBuff.pop(); - if (zipBuff.length === 0 && ltr === NUM_DELIMITER) { - parseToken(buff).forEach((item) => { - tokens.push(item); - }); - buff = ''; - } - else buff += ltr; + if (ltr === constants.ZIP_START_DELIMITER) zipBuff.push(1); + if (ltr === constants.ZIP_END_DELIMITER) zipBuff.pop(); + if (zipBuff.length === 0 && ltr === constants.NUM_DELIMITER) { + this._parseToken(buff).forEach((item) => { + tokens.push(item); + }); + buff = ''; + } else buff += ltr; } if (buff) { - parseToken(buff).forEach((item) => { - tokens.push(item); - }); + this._parseToken(buff).forEach((item) => { + tokens.push(item); + }); } - return tokens; -}; - - -/** - * Parse token from string - * - * @param token - * @return {[]} array with tokens - */ -const parseToken = token => { + } + + /** + * Parse string by delta + * + * @param string + * @return {number[]} array with tokens + * @private + */ + _parseDelta(string) { let tokens = []; - if (token.indexOf(ZIP_START_DELIMITER) > -1) { - let [base, subString] = token.split(ZIP_START_DELIMITER); - base = parseInt(base, 10); - let items = parseString(subString.slice(0, subString.length-1)); - items.forEach((item) => tokens.push(item+base)); - return tokens; - } - if (token.indexOf('-') > -1) { - let [start, stop] = token.split('-'); - start = parseInt(start, 10); - stop = parseInt(stop, 10); - - for (let i = start; i <= stop; i += 1) { - tokens.push(i); - } - } - else tokens = [parseInt(token)]; - return tokens; -}; + const chunks = this._deltaChunks(string); -/** - * Parse string by delta - * - * @param string - * @return {[]} array with tokens - */ -const parseDelta = string => { - let tokens = [], - chunks = deltaChunks(string); - - chunks.forEach((chunk) => tokens = tokens.concat(parseDeltaChunks(chunk))); + chunks.forEach((chunk) => { + tokens = tokens.concat(this._parseDeltaChunks(chunk)); + return tokens; + }); let last = 0; tokens.forEach((token, i) => { - tokens[i]=token+last; - last=tokens[i]; + tokens[i] = token + last; + last = tokens[i]; }); return tokens; -}; - - -/** - * Parse chunk of delta - * - * @param chunk - * @return {[]} array with tokens - */ -const parseDeltaChunks = chunk => { - let listBy, blocks, tokens = []; - if (chunk.startsWith(DELTA_LIST_BY_ONE)) listBy = 1; - if (chunk.startsWith(DELTA_LIST_BY_TWO)) listBy = 2; + } + + /** + * Parse token from string + * + * @param token + * @return {number[]} array with tokens + * @private + */ + _parseToken(token) { + let tokens = []; + if (token.indexOf(constants.ZIP_START_DELIMITER) > -1) { + // eslint-disable-next-line prefer-const + let [base, subString] = token.split(constants.ZIP_START_DELIMITER); + base = parseInt(base, 10); + const items = this._parseString(subString.slice(0, subString.length - 1)); + items.forEach((item) => tokens.push(item + base)); + return tokens; + } + if (token.indexOf('-') > -1) { + let [start, stop] = token.split('-'); + start = parseInt(start, this.intBase); + stop = parseInt(stop, this.intBase); + this._checkLength(Math.abs(stop - start)); + + for (let i = start; i <= stop; i += 1) { + tokens.push(i); + } + } else tokens = [parseInt(token, this.intBase)]; + + this._checkLength(tokens.length); + this.countTokens += tokens.length; + return tokens; + } + + /** + * Parse chunk of delta + * + * @param chunk + * @return {number[]} array with tokens + * @private + */ + _parseDeltaChunks(chunk) { + let listBy; + let tokens = []; + if (chunk.startsWith(constants.DELTA_LIST_BY_ONE)) listBy = 1; + if (chunk.startsWith(constants.DELTA_LIST_BY_TWO)) listBy = 2; if (listBy) chunk = chunk.slice(1); - blocks = chunk.split('x'); + const blocks = chunk.split(constants.MULTIPLICATION); if (listBy) { - if (blocks.length === 1) { - tokens = wrap(chunk, listBy); - } - else { - // Дописать функционал - } - - } - else if (blocks.length === 2) { - let num = parseInt(blocks[1], 10); - for (let i = 0; i < blocks[0]; i++) { - tokens.push(num); - } - } - else tokens = [parseInt(chunk, 10)]; + if (blocks.length === 1) { + tokens = this._wrap(chunk, listBy); + } else { + const items = blocks.map((block) => (this._wrap(block, listBy))); + items.forEach((item, i) => { + if (i > 0) { + const c = tokens.pop(); + for (let j = 0; j < c; j += 1) tokens.push(item[0]); + item = item.slice(1); + } + tokens.push(...item); + }); + return tokens; + } + } else if (blocks.length === 2) { + const num = parseInt(blocks[1], this.intBase); + for (let i = 0; i < blocks[0]; i += 1) { + tokens.push(num); + } + } else tokens = [parseInt(chunk, this.intBase)]; return tokens; - -}; - -/** - * Yield chunks for delta string - * - * @param string for split into chunks - * @return [] of chunks - */ -const deltaChunks = string => { - let chunks = [], + } + + /** + * Yield chunks for delta string + * + * @param string for split into chunks + * @return {string[]} of chunks + * @private + */ + // eslint-disable-next-line class-methods-use-this + _deltaChunks(string) { + const chunks = []; + let buf = ''; + // eslint-disable-next-line no-restricted-syntax + for (const ltr of string) { + if (ltr === constants.NUM_DELIMITER) { + (buf !== '') && chunks.push(buf); buf = ''; - for (let ltr of string) { - if (ltr === NUM_DELIMITER) { - (buf!=='') && chunks.push(buf); - buf = ''; - } - else if (ltr === DELTA_LIST_BY_ONE || ltr === DELTA_LIST_BY_TWO) { - (buf!=='') && chunks.push(buf); - buf = ltr; - } - else { - buf += ltr; - } + } else if (ltr === constants.DELTA_LIST_BY_ONE || ltr === constants.DELTA_LIST_BY_TWO) { + (buf !== '') && chunks.push(buf); + buf = ltr; + } else { + buf += ltr; + } } if (buf !== '') chunks.push(buf); return chunks; -}; - -/** - * Convert string to several strings of length of count symbols - * - * @param string - * @param count symbols - * @returns {[]} list of several strings - */ -const wrap = (string, count) => { - let list = []; - for (let i = 0; i this.maxLength) throw new RangeError('Tokens count is greater than the limit.'); + } +} + +module.exports = Decode; diff --git a/scripts/encode.js b/scripts/encode.js index 8b87a8a..38e7cf5 100644 --- a/scripts/encode.js +++ b/scripts/encode.js @@ -1,100 +1,160 @@ -/** - * Encode array tokens to string - * - * @param tokens array of tokens - * @return {string} encoding string - */ -const encode = tokens => { +const constants = require('../constants/index.js'); + +class Encode { + /** + * @param maxLength + * @return {string} + */ + constructor( + maxLength = 1000000, + ) { + this.maxLength = maxLength; + } + + /** + * Encode array tokens to string + * + * @param tokens array of tokens + * @param mode where 1 - encode to simple string, 2 - encode to delta string. + * Default: MODE_SIMPLE_STRING + * @return {string} encoding string + */ + parse(tokens, mode = constants.MODE_SIMPLE_STRING) { if (!tokens || !Array.isArray(tokens)) { - console.error('tokens argument should be an array of integers'); + throw new TypeError('tokens argument should be an array of integers'); + } + if (tokens.length > this.maxLength) { + throw new RangeError('array size is higher than allowed'); + } + if (mode === constants.MODE_SIMPLE_STRING) { + return (this._encodeString(tokens)); } + if (mode === constants.MODE_DELTA_STRING) { + return (this._encodeDelta(tokens)); + } + throw new Error('you must select 1 or 2 at second parameter (1 - simple string, 2 - delta string)'); + } + /** + * Encoding to simple string + * + * @param tokens + * @return {string} compressed string + * @private + */ + _encodeString(tokens) { if (tokens.length === 0) return ''; - if (tokens.length < 3) { - return tokens.join(','); + return tokens.join(constants.NUM_DELIMITER); } - - encodeString(tokens); - encodeDelta(tokens); -}; - -/** - * Encoding to simple string - * - * @param tokens - * @return {string} compressed string - */ -const encodeString = tokens => { + const sortedTokens = tokens.sort((a, b) => a - b); const min = tokens[0]; - const sortedtokens = tokens.sort((a, b) => a - b); let compressedString = `${min}`; let rangeStart = false; let values = []; - sortedtokens.forEach((id, index) => { - const newId = id - min; + sortedTokens.forEach((id, index) => { + const newId = id - min; - if (rangeStart === false && sortedtokens[index + 1] === id + 1) { - rangeStart = newId; - } else if (rangeStart === false && sortedtokens[index + 1] !== id + 1) { - values.push(newId); - } else if (rangeStart !== false && sortedtokens[index + 1] !== id + 1) { - values.push([rangeStart, newId]); - rangeStart = false; - } + if (rangeStart === false && sortedTokens[index + 1] === id + 1) { + rangeStart = newId; + } else if (rangeStart === false && sortedTokens[index + 1] !== id + 1) { + values.push(newId); + } else if (rangeStart !== false && sortedTokens[index + 1] !== id + 1) { + values.push([rangeStart, newId]); + rangeStart = false; + } }); - values = values.map(item => (typeof item === 'number' ? item : `${item[0]}-${item[1]}`)); - compressedString = `${compressedString}(${values.join(',')})`; - - console.log(compressedString); + values = values.map((item) => (typeof item === 'number' ? item : `${item[0]}-${item[1]}`)); + compressedString = `${compressedString}(${values.join(constants.NUM_DELIMITER)})`; return compressedString; -}; + } -/** - * Encoding to delta-string - * - * @param tokens - * @return {string} compressed string - */ -const encodeDelta = tokens => { - const sortedtokens = tokens.sort((a, b) => a - b); + /** + * Encoding to delta-string + * + * @param tokens + * @return {string} compressed string + * @private + */ + _encodeDelta(tokens) { + if (tokens.length === 0) return constants.DELTA; + const sortedTokens = this._deltaCompression(tokens); + const chunks = this._xBlocks(sortedTokens); + return this._compressToString(chunks); + } - _xBlocks(_deltaCompression(sortedtokens)); -}; + /** + * Converting array of tokens to sorted array of difference tokens + * + * @param tokens + * @return {number[]} sorted array of difference tokens + * @private + */ + _deltaCompression(tokens) { + tokens.sort((a, b) => a - b); + const diffTokens = []; + tokens.forEach((token, i) => { + i !== 0 ? diffTokens.push(token - tokens[i - 1]) : diffTokens.push(token); + }); + return diffTokens; + } -/** - * Forming x-blocks from difference tokens - * - * @param tokens - * @return {[]} - * @private - */ -const _xBlocks = tokens => { - let buf = [], - last, - tokensCopy = []; + /** + * Forming x-blocks from difference tokens + * + * @param tokens + * @return {string[]} + * @private + */ + _xBlocks(tokens) { + let buf = []; + const tokensCopy = []; tokens.forEach((token, i) => { - if (i !== 0 && (token !== tokens[i - 1] || i === tokens.length - 1)) { - buf.length > 1 ? tokensCopy.push(`${buf.length}x${buf[0]}`) : tokensCopy.push(`${buf[0]}`); - buf = [token]; - } else buf.push(token); + if (i !== 0 && (token !== tokens[i - 1] || i === tokens.length - 1)) { + buf.length > 1 ? tokensCopy.push(`${buf.length}x${buf[0]}`) : tokensCopy.push(`${buf[0]}`); + buf = [token]; + } else buf.push(token); }); if (buf.length !== 0) tokensCopy.push(`${buf}`); return tokensCopy; -}; + } + /** + * Compress chunks to delta-strings + * + * @param chunks + * @return {string} compressedString + * @private + */ + _compressToString(chunks) { + let del = constants.NUM_DELIMITER; + let newDel = null; + let compressedString = ''; -// Tests DONE -// encode([]); -// encode([1]); -// encode([1, 2]); -// encode([1, 2, 3]); -// encode([1, 2, 3, 4]); -// encode([1, 3, 5, 7]); -// encode([1, 2, 3, 5, 6, 7]); -// encode([1, 2, 3, 5, 6, 7, 9]); -_xBlocks([1, 1, 2]); + chunks.forEach((chunk) => { + if (chunk.indexOf(constants.MULTIPLICATION) > -1) { + const [x, val] = chunk.split(constants.MULTIPLICATION); + if (x.length === val.length) { + newDel = (val.length === 1 ? constants.DELTA_LIST_BY_ONE : (val.length === 2 + ? constants.DELTA_LIST_BY_TWO : constants.NUM_DELIMITER)); + } else { + newDel = constants.NUM_DELIMITER; + } + } else { + newDel = (chunk.length === 1 ? constants.DELTA_LIST_BY_ONE : (chunk.length === 2 + ? constants.DELTA_LIST_BY_TWO : constants.NUM_DELIMITER)); + } + if (newDel === constants.NUM_DELIMITER || newDel !== del) { + compressedString += newDel; + } + compressedString += chunk; + del = newDel; + }); + return `~${compressedString}`; + } +} +module.exports = Encode; diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..29ec168 --- /dev/null +++ b/test/index.js @@ -0,0 +1,68 @@ +const assert = require('assert'); +const Encode = require('../scripts/encode'); +const Decode = require('../scripts/decode'); +const { MODE_DELTA_STRING } = require('../constants'); + +const testEncode = new Encode(); +const testDecode = new Decode(); + +describe('encoding', () => { + it('For test making simple string', () => { + assert.equal(testEncode.parse([]), ''); + assert.equal(testEncode.parse([1]), '1'); + assert.equal(testEncode.parse([1, 2]), '1,2'); + assert.equal(testEncode.parse([1, 2, 3]), '1(0-2)'); + assert.equal(testEncode.parse([1, 2, 3, 4]), '1(0-3)'); + assert.equal(testEncode.parse([1, 3, 5, 7]), '1(0,2,4,6)'); + assert.equal(testEncode.parse([1, 2, 3, 5, 6, 7]), '1(0-2,4-6)'); + assert.equal(testEncode.parse([1, 2, 3, 5, 6, 7, 9]), '1(0-2,4-6,8)'); + }); + it('For test making delta string', () => { + assert.equal(testEncode.parse([], MODE_DELTA_STRING), '~'); + assert.equal(testEncode.parse([1, 3, 6], MODE_DELTA_STRING), '~.123'); + }); +}); + +describe('decoding', () => { + it('No-zipped strings', () => { + assert.deepEqual(testDecode.parse('123'), [123]); + assert.deepEqual(testDecode.parse('123, 456'), [123, 456]); + }); + it('Strings with ranges', () => { + assert.deepEqual(testDecode.parse('1-3'), [1, 2, 3]); + assert.deepEqual(testDecode.parse('1-3,5-9'), [1, 2, 3, 5, 6, 7, 8, 9]); + }); + it('Zipped strings', () => { + assert.deepEqual(testDecode.parse('120(0,3,5,8,9)'), [120, 123, 125, 128, 129]); + assert.deepEqual(testDecode.parse('12(1,4),140(0,2,5)'), [13, 16, 140, 142, 145]); + }); + it('Strings with zipped data and ranges', () => { + assert.deepEqual(testDecode.parse('120(0,3,6),130-132'), [120, 123, 126, 130, 131, 132]); + assert.deepEqual(testDecode.parse('120(0-6)'), [120, 121, 122, 123, 124, 125, 126]); + }); + it('Strings with base of numbers', () => { + assert.deepEqual(testDecode.parse('x16;3,f'), [3, 15]); + assert.deepEqual(testDecode.parse('x2;11,101'), [3, 5]); + }); + it('Base delta string', () => { + assert.deepEqual(testDecode.parse('~'), []); + assert.deepEqual(testDecode.parse('~1'), [1]); + assert.deepEqual(testDecode.parse('~155'), [155]); + assert.deepEqual(testDecode.parse('~1,2,3'), [1, 3, 6]); + }); + it('Delta string with ranges', () => { + assert.deepEqual(testDecode.parse('~.1'), [1]); + assert.deepEqual(testDecode.parse('~.123'), [1, 3, 6]); + assert.deepEqual(testDecode.parse('~.123:1012'), [1, 3, 6, 16, 28]); + assert.deepEqual(testDecode.parse('~.12:10.45'), [1, 3, 13, 17, 22]); + assert.deepEqual(testDecode.parse('~.12:10.45,146,234.14'), [1, 3, 13, 17, 22, 168, 402, 403, 407]); + }); + it('Delta strings with x-ranges', () => { + assert.deepEqual(testDecode.parse('~3x1'), [1, 2, 3]); + assert.deepEqual(testDecode.parse('~.2x12'), [1, 2, 4]); + assert.deepEqual(testDecode.parse('~.13x3'), [1, 4, 7, 10]); + assert.deepEqual(testDecode.parse('~12,3x4,'), [12, 16, 20, 24]); + assert.deepEqual(testDecode.parse('~.54x13:1010x11'), [5, 6, 7, 8, 9, 12, 22, 33, 44, 55, 66, 77, 88, 99, + 110, 121, 132]); + }); +});