Useful array extensions

9th January 2014

I try not extend the built-in prototypes of Javascript objects but there are some situations where it is for the best. Polyfilling Javascript array functions for older browsers in one of them. Some others are below:

/**
* Returns a new array of unique elements
* @return {array} unique elements from this array
*/
Array.prototype.unique = function() {
var a = [];
var l = this.length;

for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++) {
if (this[i] === this[j])
j = ++i;
}

a.push(this[i]);
}

return a;
};
/*
* Gets a given item if it exists, safe version of arr[$i]
* @return {mixed} item at given index
*/
Array.prototype.item = function(i) {
if (this.length>i) {
return this[i];
} else {
return null;
}
};
/**
* Create an array with values ranging from 'from' to 'to'
* @param {int} from value (inclusive)
* @param {int} to value (inclusive)
* @return {array} new array
*/
Array.range = function(from, to) {
var arr = [];

for (var i = from; i <= to; i++) {
arr.push(i);
}

return arr;
};
/**
* Finds a value in the array and removes it
* @param {mixed} value
*/
Array.prototype.remove = function(val) {
var index = this.indexOf(val);

if (index > -1) {
this.splice(index, 1);
}
};
/*
* Gets the most commonly occurring value in an array
* @return {mixed} most common value
*/
Array.prototype.most = function() {
var elems = [];
var counts = {};

this.forEach(function(val) {
var elemIndex = elems.indexOf(val);

if (elemIndex>=0) {
counts[elemIndex]++;
} else {
counts[elems.length] = 1;
elems.push(val);
}
});

//sort the new bunch of elems based on the count
var maxCount = 0, maxIndex = 0;
counts.forEach(function(val, key) {
if (val>maxCount) {
maxCount = val;
maxIndex = key;
}
});

//combine the count and the
return elems[maxIndex];
};
/**
* Get a random element from this array
* @return {mixed} random attribute
*/
Array.prototype.getRandom = function(iMin, iMax) {
if (iMin===undefined) {
iMin = 0;
}

if (iMax===undefined) {
iMax = this.length;
}

iMax = Math.min(Math.max(0, iMax), this.length);
iMin = Math.min(Math.max(0, iMin), iMax);

if (this.length>0) {
return this[Math.floor(Number.random()*(iMax-iMin) + iMin))];
}
};
/**
* Find the index of an item using a binary search
* If the item cannot be found, then the index of the closest match is returned
* Note: assumes the array is already sorted
*
* @param {mixed} value to search for
* @param {function} custom sorting function (optional)
* @return {integer} index
*/
Array.prototype.binarySearch = function(mVal, fCustomFunc) {
if (this.length==0) {
return 0;
}

var aArr = this;

//recursive method for searching partitions
function binarySearchP(mVal, iStart, iEnd, fCustomFunc) {
if (iStart >= iEnd) {
return iStart;
}

var iThisLength = iEnd - iStart;

//check the half way point
var iHalfPoint = Math.floor(iThisLength/2.0) + iStart;

if (fCustomFunc === undefined) {
if (mVal > aArr[iHalfPoint]) {
var nResult = 1;
} else if (mVal == aArr[iHalfPoint]) {
var nResult = 0;
} else {
var nResult = -1;
}
} else {
var nResult = fCustomFunc(aArr[iHalfPoint], mVal);
}

//either continue the search or return the result
if (nResult == 0) {
return iHalfPoint;
} else if (nResult > 0) {
if (iThisLength > 1) {
return binarySearchP(mVal, iHalfPoint+1, iEnd);
} else {
return iHalfPoint+1;
}
} else {
if (iThisLength > 1) {
return binarySearchP(mVal, iStart, iHalfPoint);
} else {
return iHalfPoint;
}
}
}

return binarySearchP(mVal, 0, aArr.length-1, fCustomFunc);
};
/**
* Get the difference of this array to the contents of another
* This is a slow differencing method that can difference unsortable arrays
* @param {array} comparison array
* @param {boolean} include rest of comparison array
* @param {boolean} use strict equality
* @return {array} difference array
*/
Array.prototype.difference = function(aArr, bIncRest, bStrictEquals) {
if (bIncRest===undefined) {
bIncRest = false;
}

if (bStrictEquals===undefined) {
bStrictEquals = true;
}

var aResult = [];

//iterate through this array and check whether each value is in the given array
for (var i=0; i<this.length; i++) {
var bInArr = false;

for (var j=0; j<aArr.length; j++) {
if (bStrictEquals) {
if (this[i] === aArr[j]) {
bInArr = true;
break;
}
} else {
if (this[i] == aArr[j]) {
bInArr = true;
break;
}
}
}

if (!bInArr) {
aResult.push(this[i]);
}
}

//iterate through the given array and check whether each value is in this array
if (bIncRest) {
for (var i=0; i<aArr.length; i++) {
var bInArr = false;

for (var j=0; j<this.length; j++) {
if (bStrictEquals) {
if (this[j] === aArr[i]) {
bInArr = true;
break;
}
} else {
if (this[j] == aArr[i]) {
bInArr = true;
break;
}
}
}

if (!bInArr) {
aResult.push(arr[i]);
}
}
}

return aResult;
};
/**
* Get the difference of this array to the contents of another
* Note: this method pre-sorts the arrays
* Note: the remaining objects are sorted
* @param {array} comparison array
* @param {boolean} include rest of comparison array
* @param {function} custom sorting function
* @return {array} difference array
*/
Array.prototype.differenceSort = function(aArr, bIncRest, fSortFunc) {
if (bIncRest===undefined) {
bIncRest = false;
}

if (fSortFunc===undefined) {
fSortFunc = function(nA, nB) {
return (nA-nB);
};
}

//sort the arrays so we can difference the two arrays efficiently (in a linear manner)
var aArr1 = this.sort(fSortFunc);
var aArr2 = aArr.sort(fSortFunc);

var i=0, j=0;
var aDiff = [];

//step through the second array until we reach the start of the first array
if (bIncRest) {
while (j<aArr2.length && fSortFunc(aArr1[i], aArr2[j])>0) {
aDiff.push(aArr2[j]);
j++;
}
} else {
while (j<aArr2.length && fSortFunc(aArr1[i], aArr2[j])>0) {
j++;
}
}

//step through the two arrays at the same time and decide whether the values are the same
while (i<aArr1.length && j<aArr2.length) {
var iResult = fSortFunc(aArr1[i], aArr2[j]);
if (iResult<0) {
aDiff.push(aArr1[i]);
i++;
} else if (iResult===0) {
i++;
} else {
j++;
}
}

//accumulate the remainder of the first array, still comparing
j--;
while (i<aArr1.length) {
if (fSortFunc(aArr1[i], aArr2[j])<0) {
aDiff.push(aArr1[i]);
i++;
} else {
i++;
}
}
j++;

//accumulate the remainder of the second array if we want to include it
if (bIncRest) {
i--;
while (j<aArr2.length) {
if (fSortFunc(aArr1[i], aArr2[j])<0) {
aDiff.push(aArr2[j]);
j++;
} else {
j++;
}
}
i++;
}

return aDiff;
};
/**
* Filter the array by their containing objects values
* @param {string} object parameter to filter by
* @param {mixed} object value to match to (infinite)
* @return {object} hashtable of results
*/
Array.prototype.filterObjsByVal = function(param) {
var objMap = {};

//accumulate the values from the parameters
var valuesArr = [];
for (var i=1; i<arguments.length; i++) {
valuesArr.push(arguments[i]);
}

//make a map from all of the objects with the chosen key
for (var i=0; i<this.length; i++) {
var thisObj = this[i];

if (thisObj[param] in objMap) {
objMap[thisObj[param]].push(thisObj);
} else {
objMap[thisObj[param]] = [thisObj];
}
}

return Object.filterByKeys(objMap, valuesArr);
};
/**
* Convert an array to an object using the values of the array as the keys in the object
* @return {object} hastable of the array
*/
Array.prototype.toObject = function() {
var newObj = {};

for (var i=0; i<this.length; i++) {
newObj[this[i].toString()] = null;
}

return newObj;
};

Comments

Note: nowadays I would recommend using a utility library like lodash.js instead of extending the Array prototype.
Andrew Lowndes - 18th Oct 2016 18:45

Make a comment

Contribute to this article and have your say.