Five Methods Missing from JavaScript Array
JavaScript is a computer programming language that most often runs
inside a web browser as part of a web page. It is a simple yet powerful
language that includes several built-in classes and methods. One of
these built-in classes is the Array
class which has
powerful methods such as concat
, sort
,
slice
, and splice
. However, there are several
useful methods, including binarySearch
,
addAll
, retainAll
, removeAll
, and
retainUnique
, that were not included in the
Array
class. This article shows you how to add these five
useful methods to the JavaScript Array
class. If you put
these methods in a JavaScript file named array.js
, you will
be able to include and use them in a web page whenever you want.
Descriptions of the Five Methods
- binarySearch
The existing
Array.indexOf
method searches an array for an occurrence of some value called a key. The existingindexOf
method is an implementation of an algorithm called linear search. This means thatindexOf
searches an array from the beginning to the end of the array, stopping when it finds the key or reaches the end of the array. If the key is stored in the array, theindexOf
method returns the index of the first occurrence of the key. Otherwise, it returns −1. The linear search algorithm (andindexOf
) works well for arrays that are small (perhaps less than 100 elements).If the elements in an array are sorted, then the computer can use a faster algorithm called binary search to find an element within that array. The
binarySearch
method works by comparing the key to the middle most element in the array. If the key is less than the middle most element, then the search is repeated in the first half of the array. If the key is greater than the middle most element, then the search is repeated in the last half of the array. Of course, if the key is equal to the middle most element, then the computer has found the key and the search is done. This process of comparing the key to the middle most element of the current interval is repeated until the computer finds the key or the interval has shrunk to only one element. If that one element is not the same as the key, then the key is not present in the array.- addAll
- The
addAll
method is similar to the built-in concat method, but theaddAll
method adds elements to an existing array instead of returning a new array as the concat method does. TheaddAll
method can be used to compute the union of arrays. - retainAll
- The
retainAll
method retains in an array all the elements that are also contained in a second array. This is similar to computing the intersection of the two arrays. - removeAll
- The
removeAll
method removes from an array all the elements that are also contained in another array. This is similar to computing the complement of the first array relative to the second. - distinct
- If an array is sorted, the
distinct
method removes all duplicates of all elements, leaving one unique entry for each element. In other words, after thedistinct
method is finished all elements in the array will be unique. This is the same thing that the -u option of the unix sort command does.
Source Code for the Five Methods
Here are the five missing JavaScript Array
methods. Copy
these methods into a text file named "array.js
". Notice
that all the methods except addAll
have an optional
parameter which is a comparison method. If your program is storing
complex objects in arrays, you can write your own comparison method to
compare the objects any way you like.
"use strict"; /** If this array contains key, returns the index of any * occurrence of key; otherwise returns -insertion - 1, * where insertion is the location within the array at * which the key should be inserted. binarySearch assumes * this array is already sorted. */ Array.prototype.binarySearch = function(key, compare) { if (typeof(compare) == 'undefined') { compare = ascending; } let left = 0; let right = this.length - 1; while (left <= right) { let mid = left + ((right - left) >>> 1); let cmp = compare(key, this[mid]); if (cmp > 0) left = mid + 1; else if (cmp < 0) right = mid - 1; else return mid; } return -(left + 1); } /** Adds all the elements in the * specified arrays to this array. */ Array.prototype.addAll = function() { for (let a = 0; a < arguments.length; a++) { let arr = arguments[a]; for (let i = 0; i < arr.length; i++) { this.push(arr[i]); } } } /** Retains in this array all the elements * that are also found in the specified array. */ Array.prototype.retainAll = function(arr, compare) { if (typeof(compare) == 'undefined') { compare = ascending; } let i = 0; while (i < this.length) { if (arr.indexOf(this[i], compare) == -1) { let end = i + 1; while (end < this.length && arr.indexOf(this[end], compare) == -1) { end++; } this.splice(i, end - i); } else { i++; } } } /** Removes from this array all the elements * that are also found in the specified array. */ Array.prototype.removeAll = function(arr, compare) { if (typeof(compare) == 'undefined') { compare = ascending; } let i = 0; while (i < this.length) { if (arr.indexOf(this[i], compare) != -1) { let end = i + 1; while (end < this.length && arr.indexOf(this[end], compare) != -1) { end++; } this.splice(i, end - i); } else { i++; } } } /** Makes all elements in this array unique. In other * words, removes all duplicate elements from this * array. Assumes this array is already sorted. */ Array.prototype.distinct = function(compare) { if (typeof(compare) == 'undefined') { compare = ascending; } let dst = 0; // Destination for elements let src = 0; // Source of elements let limit = this.length - 1; while (src < limit) { while (compare(this[src], this[src + 1]) == 0) { if (++src == limit) { break; } } this[dst++] = this[src++]; } if (src == limit) { this[dst++] = this[src]; } this.length = dst; } /** Compares two objects using * built-in JavaScript operators. */ function ascending(a, b) { if (a < b) return -1; else if (a > b) return 1; return 0; }
Using the Five Methods
To use the five methods, include in the head of an HTML document, the
array.js
file that you saved as shown on
line 9 of the next
code example. Then you can write JavaScript code to use the five methods
just like you would use other built-in JavaScript Array
methods. The testArray
function at
lines 11–87 in the
next code example shows how to use the five missing methods.
<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <title>Using the Five Methods</title> <!-- Import the JavaScript code from array.js so that the browser will add the five methods to the Array class. --> <script src="array.js"></script> <script> "use strict"; function testArray() { document.open(); // Create an array named plants. let plants = ['apple', 'pear', 'plum', 'peach']; document.writeln('plants: ' + plants + '<br>'); document.writeln('<br>'); // Use the indexOf() method. document.writeln('indexOf()<br>'); let possible = 'plum'; if (plants.indexOf(possible) >= 0) document.write('plants contains ' + possible); else document.write('plants does not contain ' + possible); document.writeln('<br>'); possible = 'cherry'; if (plants.indexOf(possible) >= 0) document.write('plants contains ' + possible); else document.write('plants does not contain ' + possible); document.writeln('<br>'); document.writeln('<br>'); // Use the addAll() method. let listX = ['cherry', 'banana', 'apricot', 'mango']; plants.addAll(listX); document.writeln('addAll()<br>'); document.writeln('listX: ' + listX + '<br>'); document.writeln('plants: ' + plants + '<br>'); document.writeln('<br>'); // Use the binarySearch() method. document.writeln('binarySearch()<br>'); possible = 'peach'; plants.sort(); if (plants.binarySearch(possible) >= 0) document.write('plants contains ' + possible); else document.write('plants does not contain ' + possible); document.writeln('<br>'); possible = 'coconut'; if (plants.binarySearch(possible) >= 0) document.write('plants contains ' + possible); else document.write('plants does not contain ' + possible); document.writeln('<br>'); document.writeln('<br>'); // Use the addAll() method. let listY = ['elm', 'pine', 'rose', 'lilac']; let listZ = ['lilac', 'pine', 'fir']; plants.addAll(listY, listZ); document.writeln('addAll()<br>'); document.writeln('listY: ' + listY + '<br>'); document.writeln('listZ: ' + listZ + '<br>'); document.writeln('plants: ' + plants + '<br>'); document.writeln('<br>'); // Use the retainAll() method. listY.retainAll(listZ); document.writeln('retainAll()<br>'); document.writeln('listY: ' + listY + '<br>'); document.writeln('<br>'); // Use the removeAll() method. listY = ['elm', 'pine', 'rose', 'lilac']; listY.removeAll(listZ); document.writeln('removeAll()<br>'); document.writeln('listY: ' + listY + '<br>'); document.writeln('<br>'); // Use the distinct() method. plants.addAll(listX, listY, listZ); plants.sort(); plants.distinct(); document.writeln('distinct()<br>'); document.writeln('plants: ' + plants + '<br>'); document.close(); } </script> </head> <body onload="testArray()"> </body> </html>
Futher Reading
Some readers have pointed out that within the
binarySearch
method, the line of code that computes the
midpoint is unnecessarily complex and inefficient. Here is the line of
code:
let mid = left + ((right - left) >>> 1);
Some readers have stated that a simpler and more efficient line of code would be this:
let mid = (left + right) / 2;
However, if we use the simpler formula for computing the midpoint for
large arrays, left + right may overflow an
integer which would cause the binarySearch
method to fail.
If you wish to read more about this possible overflow,
Overflow Bug in Binary Search
by Sourabh Gupta is a short clear article that explains it. In addition,
the binary search algorithm and computing the midpoint is explained in
both of the following books:
- Programming Fundamentals in JavaScript in Chapter 10 in the "Binary Search" section
- Advanced Programming Techniques in Chapter 1 in the "Binary Search" section