diff --git a/readme.md b/readme.md index 4946ee3..becd297 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,7 @@ Javascript utility functions for web development * [min-max](https://gen-tech.github.io/js-utils/modules/_min_max_.html) * [noop](https://gen-tech.github.io/js-utils/modules/_noop_.html) * [promise-timeout](https://gen-tech.github.io/js-utils/modules/_promise_timeout_.html) +* [replace](https://gen-tech.github.io/js-utils/modules/_replace_.html) * [revert-promise](https://gen-tech.github.io/js-utils/modules/_revert_promise_.html) * [unique-id](https://gen-tech.github.io/js-utils/modules/_unique_id_.html) diff --git a/src/as-proto/array-replace.spec.ts b/src/as-proto/array-replace.spec.ts new file mode 100644 index 0000000..0ee6411 --- /dev/null +++ b/src/as-proto/array-replace.spec.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import 'mocha'; + +import "./array-replace"; +import { replace } from "../replace"; + +describe('Array Replace Proto Function', () => { + it('should work as same', () => { + const foo = [1, 2, 3, 1, 2, 3]; + + expect(foo.replace(2, 100)).to.eql(replace(foo, 2, 100)); + expect(foo.replace(4, 100)).to.eql(replace(foo, 4, 100)); + + const itemOptions = {startingIndex: 2, deleteCount: 2, itemsToReplace: [100, 101]}; + expect(foo.replace(itemOptions)).to.eql(replace(foo, itemOptions)); + + const mapOptions = {itemsToReplace: new Map([[2, 100], [3, 101]])}; + expect(foo.replace(mapOptions)).to.eql(replace(foo, mapOptions)); + + const mapOptionsMulti = {itemsToReplace: new Map([[2, 100], [3, 101]]), multi: true}; + expect(foo.replace(mapOptionsMulti)).to.eql(replace(foo, mapOptionsMulti)); + }); +}); diff --git a/src/as-proto/array-replace.ts b/src/as-proto/array-replace.ts new file mode 100644 index 0000000..883f9c7 --- /dev/null +++ b/src/as-proto/array-replace.ts @@ -0,0 +1,79 @@ +import { replace, ReplaceItemsOptions, ReplaceByMapOptions } from "../replace"; + +declare global { + export interface Array { + /** + * #### Replace + * + * Replaces an item with passed one of an array + * + * * * * + * Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * array.replace("b", "x"); // ["a", "x", "c", "a", "b", "c"] + * ``` + * * * * + * @param array Array to replace its item + * @param itemToRemove Item to remove + * @param itemToReplace Item to replace with + * @return New replaced array + */ + replace(itemToRemove: T, itemToReplace: T): T[]; + + /** + * #### Replace + * + * Deletes items and replaces new ones from passed starting index of an array + * + * * * * + * Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * array.replace({startingIndex: 3, deleteCount: 1, itemsToReplace: ['x', 'y']}); // ["a", "b", "c", "x", "y", "b", "c"]; + * ``` + * * * * + * @param array Array to replace its item + * @param replaceOptions Replace options + * @template T Typeof array items + * @return New replaced array + */ + replace(replaceOptions: ReplaceItemsOptions): T[]; + + /** + * #### Replace + * + * Deletes items and replaces new ones by passed matcher map + * + * * * * + * Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * const map = new Map(); + * map.set("a", "x") + * map.set("b", "y"); + * + * array.replace({itemsToReplace: map}); // ["x", "y", "c", "a", "b", "c"]; + * array.replace({itemsToReplace: map, multi: true}); // ["x", "y", "c", "x", "y", "c"]; + * ``` + * * * * + * @param array Array to replace its item + * @param replaceOptions Replace options + * @template T Typeof array items + * @return New replaced array + */ + replace(replaceOptions: ReplaceByMapOptions): T[]; + } +} + +Array.prototype.replace = function(options: T | ReplaceItemsOptions | ReplaceByMapOptions, itemToReplace: T): T[] { + return replace.call(null, this, ...arguments); +} diff --git a/src/index.ts b/src/index.ts index 6d54de4..8a35354 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,4 +10,6 @@ export * from "./min-max"; export * from "./noop"; export * from "./open-promise"; export * from "./promise-timeout"; +export * from "./replace"; +export { revertPromise } from "./revert-promise"; export * from "./unique-id"; diff --git a/src/replace.spec.ts b/src/replace.spec.ts new file mode 100644 index 0000000..cf6c42f --- /dev/null +++ b/src/replace.spec.ts @@ -0,0 +1,44 @@ +import { expect } from 'chai'; +import 'mocha'; + +import { replace } from "./replace"; + +describe('Replace Function', () => { + let foo: number[]; + + beforeEach(() => { + foo = [1, 2, 3, 1, 2, 3]; + }); + + it('should replace one item successfully', () => { + expect(replace(foo, 2, 100)).to.eql([1, 100, 3, 1, 2, 3]); + }); + + it('shouldn\'t replace nonfound item', () => { + expect(replace(foo, 4, 100)).to.eql([1, 2, 3, 1, 2, 3]); + }); + + it('should replace items from index 0 when startingIndex is not defined', () => { + expect(replace(foo, {itemsToReplace: [100]})).to.eql([100, 2, 3, 1, 2, 3]); + }); + + it('should add items when itemsToReplace array contains multiple elements', () => { + expect(replace(foo, {itemsToReplace: [100, 101, 102]})).to.eql([100, 101, 102, 2, 3, 1, 2, 3]); + }); + + it('should delete items as defined', () => { + expect(replace(foo, {itemsToReplace: [100], deleteCount: 2})).to.eql([100, 3, 1, 2, 3]); + }); + + it('should start replacing from startingIndex', () => { + expect(replace(foo, { startingIndex: 2, itemsToReplace: [100]})).to.eql([1, 2, 100, 1, 2, 3]); + }); + + it('should replace by using mode: `replace by map`', () => { + expect(replace(foo, {itemsToReplace: new Map([[2, 100]])})).to.eql([1, 100, 3, 1, 2, 3]); + }); + + it('should replace multiple when multi set as true', () => { + expect(replace(foo, {itemsToReplace: new Map([[2, 100]]), multi: true})).to.eql([1, 100, 3, 1, 100, 3]); + }); +}); diff --git a/src/replace.ts b/src/replace.ts new file mode 100644 index 0000000..822d6b5 --- /dev/null +++ b/src/replace.ts @@ -0,0 +1,226 @@ +/** + * Replace Items Options Interface + * + * @template T typeof the items of the array + */ +export interface ReplaceItemsOptions { + /** + * Starting index of the array for replacement operation + * + * @default 0 + */ + startingIndex?: number; + + /** + * Determines how many items will be deleted from the array + * + * @default 1 + */ + deleteCount?: number; + + /** + * Items to replace + * + * @default [] + */ + itemsToReplace?: T[]; +} + +/** + * Replace by Item Matcher Map Options Interface + * + * @template T typeof the items of the array + */ +export interface ReplaceByMapOptions { + /** + * Items' Matcher Map to replace + */ + itemsToReplace: Map; + + /** + * Set as `true` to replace all items in the array if matches with the key of the matcher map while iterating + * + * @default false + */ + multi?: boolean; +} + +/** + * Default Options for Replace Items Mode + */ +const REPLACE_ITEMS_DEFAULT_OPTIONS: Required> = { + deleteCount: 1, + itemsToReplace: [], + startingIndex: 0 +}; + +/** + * Default Options for Replace By Map Mode + */ +const REPLACE_BY_MAP_DEFAULT_OPTIONS: Required> = { + itemsToReplace: new Map(), + multi: false +}; + +/** + * #### Replace + * + * Replaces an item with passed one of an array + * + * * * * + * Example: + * ```typescript + * import { replace } from "@gen-tech/js-utils"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * replace(array, "b", "x"); // ["a", "x", "c", "a", "b", "c"] + * ``` + * Array Prototype Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * array.replace("b", "x"); // ["a", "x", "c", "a", "b", "c"] + * ``` + * * * * + * @param array Array to replace its item + * @param itemToRemove Item to remove + * @param itemToReplace Item to replace with + * @template T Typeof array items + * @return New replaced array + */ +export function replace(array: T[], itemToRemove: T, itemToReplace: T): T[]; +/** + * #### Replace + * + * Deletes items and replaces new ones from passed starting index of an array + * + * * * * + * Example: + * ```typescript + * import { replace } from "@gen-tech/js-utils"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * replace(array, {startingIndex: 3, deleteCount: 1, itemsToReplace: ['x', 'y']}); // ["a", "b", "c", "x", "y", "b", "c"]; + * ``` + * Array Prototype Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * + * array.replace({startingIndex: 3, deleteCount: 1, itemsToReplace: ['x', 'y']}); // ["a", "b", "c", "x", "y", "b", "c"]; + * ``` + * * * * + * @param array Array to replace its item + * @param replaceOptions Replace options + * @template T Typeof array items + * @return New replaced array + */ +export function replace(array: T[], replaceOptions: ReplaceItemsOptions): T[]; +/** + * #### Replace + * + * Deletes items and replaces new ones by passed matcher map + * + * * * * + * Example: + * ```typescript + * import { replace } from "@gen-tech/js-utils"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * const map = new Map(); + * map.set("a", "x") + * map.set("b", "y"); + * + * replace(array, {itemsToReplace: map}); // ["x", "y", "c", "a", "b", "c"]; + * replace(array, {itemsToReplace: map, multi: true}); // ["x", "y", "c", "x", "y", "c"]; + * ``` + * Array Prototype Example: + * ```typescript + * import "@gen-tech/js-utils/dist/as-proto/array-replace"; + * + * const array = ["a", "b", "c", "a", "b", "c"]; + * const map = new Map(); + * map.set("a", "x") + * map.set("b", "y"); + * + * array.replace({itemsToReplace: map}); // ["x", "y", "c", "a", "b", "c"]; + * array.replace({itemsToReplace: map, multi: true}); // ["x", "y", "c", "x", "y", "c"]; + * ``` + * * * * + * @param array Array to replace its item + * @param replaceOptions Replace options + * @template T Typeof array items + * @return New replaced array + */ +export function replace(array: T[], replaceOptions: ReplaceByMapOptions): T[]; +export function replace( + array: T[], + options: T | ReplaceItemsOptions | ReplaceByMapOptions, + itemToReplace?: T +): T[] { + if (arguments.length > 2) { + options = >{ + startingIndex: array.indexOf(options), + itemsToReplace: [itemToReplace] + }; + } + + if (( | ReplaceItemsOptions>options).itemsToReplace instanceof Array) { + return _replaceItems(array, >options); + } else { + return _replaceByMap(array, >options); + } +} + +/** + * Replace items by 'replace items' mode + * + * @param array Array to replace items + * @param options Replace options + */ +function _replaceItems(array: T[], options: ReplaceItemsOptions): T[] { + const { startingIndex, deleteCount, itemsToReplace}: Required> = Object.assign({}, REPLACE_ITEMS_DEFAULT_OPTIONS, options); + + const newArray = [...array]; + + // If item not found or index number is negative; do nothing + if (startingIndex < 0) { + return newArray; + } + + newArray.splice(startingIndex, deleteCount, ...itemsToReplace); + + return newArray; +} + +/** + * Replace items by 'replace by map' mode + * + * @param array Array to replace items + * @param options Replace options + */ +function _replaceByMap(array: T[], options: ReplaceByMapOptions): T[] { + const { itemsToReplace, multi }: Required> = Object.assign({}, REPLACE_BY_MAP_DEFAULT_OPTIONS, options); + const iterationMethod = multi ? Array.prototype.forEach : Array.prototype.some; + + const newArray = [...array]; + + itemsToReplace.forEach((value, key) => { + iterationMethod.call(newArray, (item, index) => { + const matches = item === key; + + if (matches) { + newArray.splice(index, 1, value); + } + + return matches; + }); + }); + + return newArray; +} diff --git a/tsconfig.json b/tsconfig.json index a1e1c8f..bd826f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "allowSyntheticDefaultImports": true, "declaration": true, "noResolve": false, + "downlevelIteration": true, "lib": [ "dom", "es2015"