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]);
+ });
+});