Last active
February 22, 2021 17:24
-
-
Save petsel/c47c7bd5a2f8ae82e20e7c689cd2fd82 to your computer and use it in GitHub Desktop.
Implementation of 3 condition-function based, prototypal `Array` methods which do mutate each their processed array reference.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* [Array.removeFirstMatch.removeLastMatch.removeEveryMatch.js] | |
* | |
* Implementation of 3 condition-function based, prototypal `Array` methods | |
* which do mutate each their processed array reference. | |
* | |
* - `Array.prototype.removeFirstMatch`, | |
* - `Array.prototype.removeLastMatch`, | |
* - `Array.prototype.removeEveryMatch` | |
* | |
*/ | |
(function (ReferenceError, TypeError, Object, Set, Array) { | |
const arrPrototype = Array.prototype; | |
const { isArray } = Array; | |
const FUNCTION_TYPE = (typeof Object); | |
function isFunction(type) { | |
/* eslint-disable valid-typeof */ | |
return ( | |
(typeof type === FUNCTION_TYPE) | |
&& (typeof type.call === FUNCTION_TYPE) | |
&& (typeof type.apply === FUNCTION_TYPE) | |
); | |
/* eslint-enable valid-typeof */ | |
} | |
function getSanitizedTarget(target) { | |
/* eslint-disable */ | |
/* jshint ignore:start */ | |
/* ignore jslint start */ | |
/* no-eq-null */ | |
return ((target != null) && target) || null; | |
/* ignore jslint end */ | |
/* jshint ignore:end */ | |
/* eslint-enable */ | |
} | |
function collectSpliceRange(rangeList, currentSplicePosition, idx, splicePositionList) { | |
let range; | |
const recentSplicePosition = splicePositionList[idx - 1] ?? null; | |
const nextSplicePosition = splicePositionList[idx + 1] ?? null; | |
const isOpenNewRange = ( | |
(recentSplicePosition === null) || | |
(currentSplicePosition - recentSplicePosition !== 1) | |
); | |
const isTerminateRange = (recentSplicePosition !== null) && ( | |
(nextSplicePosition === null) || | |
(nextSplicePosition - currentSplicePosition !== 1) | |
); | |
if (isOpenNewRange) { | |
range = [ currentSplicePosition ]; | |
rangeList.push(range); | |
} | |
if (isTerminateRange) { | |
range = rangeList[rangeList.length - 1]; | |
range.push(currentSplicePosition); | |
} | |
return rangeList; | |
} | |
function createSpliceRangeList(splicePositionList) { | |
return splicePositionList | |
// create list of splice ranges. | |
.reduce(collectSpliceRange, []) | |
// in order to be able to splice/delete | |
// items from each target array's right side. | |
.reverse(); | |
} | |
function removeTargetItemsBySpliceRange([idxStart, idxEnd]) { | |
// delete each of a target array's range of items. | |
this.splice(idxStart, (idxEnd - idxStart + 1)) | |
} | |
function removeTargetItemsBySpliceRangeList(rangeList, target) { | |
rangeList | |
// mutate each of a scheme's original target array. | |
.forEach(removeTargetItemsBySpliceRange, target); | |
return target; | |
} | |
function removeFirstMatchingItemByCondition(arr, condition, target) { | |
let idx = -1; | |
const count = arr.length - 1; | |
// eslint-disable-next-line no-plusplus | |
while (++idx <= count) { | |
// eslint-disable-next-line no-prototype-builtins | |
if (arr.hasOwnProperty(idx)) { // take *sparse array* into account. | |
// arguments list ... [elm, idx, arr] ... called within `target` context. | |
if (condition.call(target, arr[idx], idx, arr)) { | |
arr.splice(idx, 1); // mutate processed array. | |
idx = count; // implementation just does not use `break`. | |
} | |
} | |
} | |
return arr; | |
} | |
function removeLastMatchingItemByCondition(arr, condition, target) { | |
let idx = arr.length; | |
while (idx) { | |
// eslint-disable-next-line no-plusplus,no-prototype-builtins | |
if (arr.hasOwnProperty(--idx)) { // take *sparse array* into account. | |
// arguments list ... [elm, idx, arr] ... called within `target` context. | |
if (condition.call(target, arr[idx], idx, arr)) { | |
arr.splice(idx, 1); // mutate processed array. | |
idx = 0; // implementation just does not use `break`. | |
} | |
} | |
} | |
return arr; | |
} | |
function removeEveryMatchingItemByCondition(arr, condition, target) { | |
let idx = arr.length; | |
const copy = [...arr]; | |
// Processing the array from RIGHT to LEFT keeps the `idx` always in sync | |
// with both related array items, the one of the mutated and also the one | |
// of the unmutated version of the processed array reference. | |
// Thus the `condition` always gets passed the unmutated shallow copy. | |
while (idx) { | |
// eslint-disable-next-line no-plusplus,no-prototype-builtins | |
if (arr.hasOwnProperty(--idx)) { // take *sparse array* into account. | |
// arguments list ... [elm, idx, arr] ... called within `target` context. | |
if (condition.call(target, copy[idx], idx, copy)) { // keep processing the unmutated array by the condition method. | |
arr.splice(idx, 1); // mutate processed array. | |
} | |
} | |
} | |
return arr; | |
} | |
function removeEveryMatchingItemByConditionAndSpliceList(arr, condition, target) { | |
const splicePositionList = arr.reduce((list, item, idx, reference) => { | |
if (condition.call(target, item, idx, reference)) { | |
list.push(idx); | |
} | |
return list; | |
}, []).sort((a, b) => a - b); // always assure ascending numerical order of indices. | |
return removeTargetItemsBySpliceRangeList( | |
// always assure a list/set of unique indices. | |
[...new Set(createSpliceRangeList(splicePositionList))], | |
arr | |
); | |
} | |
function removeFirstMatch(condition, target) { | |
if (isArray(this)) { | |
if (isFunction(condition)) { | |
removeFirstMatchingItemByCondition(this, condition, getSanitizedTarget(target)); | |
} else { | |
throw (new TypeError('"removeFirstMatch" needs to be provided a `Function` type condition to.')); | |
} | |
} else { | |
throw (new ReferenceError('"removeFirstMatch" has to be processed within an `Array` type context.')); | |
} | |
return this; | |
} | |
Object.defineProperty(arrPrototype, 'removeFirstMatch', { | |
configurable: true, | |
writable: true, | |
value: removeFirstMatch | |
}); | |
Object.defineProperty(arrPrototype.removeFirstMatch, 'toString', { | |
value: () => 'function removeFirstMatch() { [custom code] }' | |
}); | |
// // provide static implementation as well. | |
// | |
// function staticRemoveFirstMatch(arr, condition, target) { | |
// return removeFirstMatch.call(arr, condition, target); | |
// } | |
// | |
// Object.defineProperty(Array, 'removeFirstMatch', { | |
// configurable: true, | |
// writable: true, | |
// value: staticRemoveFirstMatch | |
// }); | |
// Object.defineProperty(Array.removeFirstMatch, 'toString', { | |
// value: () => 'function removeFirstMatch() { [custom code] }' | |
// }); | |
function removeLastMatch(condition, target) { | |
if (isArray(this)) { | |
if (isFunction(condition)) { | |
removeLastMatchingItemByCondition(this, condition, getSanitizedTarget(target)); | |
} else { | |
throw (new TypeError('"removeLastMatch" needs to be provided a `Function` type condition to.')); | |
} | |
} else { | |
throw (new ReferenceError('"removeLastMatch" has to be processed within an `Array` type context.')); | |
} | |
return this; | |
} | |
Object.defineProperty(arrPrototype, 'removeLastMatch', { | |
configurable: true, | |
writable: true, | |
value: removeLastMatch | |
}); | |
Object.defineProperty(arrPrototype.removeLastMatch, 'toString', { | |
value: () => 'function removeLastMatch() { [custom code] }' | |
}); | |
// // provide static implementation as well. | |
// | |
// function staticRemoveLastMatch(arr, condition, target) { | |
// return removeLastMatch.call(arr, condition, target); | |
// } | |
// | |
// Object.defineProperty(Array, 'removeLastMatch', { | |
// configurable: true, | |
// writable: true, | |
// value: staticRemoveLastMatch | |
// }); | |
// Object.defineProperty(Array.removeLastMatch, 'toString', { | |
// value: () => 'function removeLastMatch() { [custom code] }' | |
// }); | |
function removeEveryMatch(condition, target) { | |
if (isArray(this)) { | |
if (isFunction(condition)) { | |
if (this.length > 100) { // an arbitrarily chosen threshold value. | |
// optimized splicing process from right to left by first running a task which maps slice ranges. | |
removeEveryMatchingItemByConditionAndSpliceList(this, condition, getSanitizedTarget(target)); | |
} else { | |
// efficient enough splicing process from right to left at direct condition matches. | |
removeEveryMatchingItemByCondition(this, condition, getSanitizedTarget(target)); | |
} | |
} else { | |
throw (new TypeError('"removeEveryMatch" needs to be provided a `Function` type condition to.')); | |
} | |
} else { | |
throw (new ReferenceError('"removeEveryMatch" has to be processed within an `Array` type context.')); | |
} | |
return this; | |
} | |
Object.defineProperty(arrPrototype, 'removeEveryMatch', { | |
configurable: true, | |
writable: true, | |
value: removeEveryMatch | |
}); | |
Object.defineProperty(arrPrototype.removeEveryMatch, 'toString', { | |
value: () => 'function removeEveryMatch() { [custom code] }' | |
}); | |
// // provide static implementation as well. | |
// | |
// function staticRemoveEveryMatch(arr, condition, target) { | |
// return removeEveryMatch.call(arr, condition, target); | |
// } | |
// | |
// Object.defineProperty(Array, 'removeEveryMatch', { | |
// configurable: true, | |
// writable: true, | |
// value: staticRemoveEveryMatch | |
// }); | |
// Object.defineProperty(Array.removeEveryMatch, 'toString', { | |
// value: () => 'function removeEveryMatch() { [custom code] }' | |
// }); | |
// return Array; | |
}(ReferenceError, TypeError, Object, Set, Array)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment