Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petsel/c47c7bd5a2f8ae82e20e7c689cd2fd82 to your computer and use it in GitHub Desktop.
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.
/**
*
* [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