4umi.com/web/javascript/array

Array functions

Basic Javascript

The built-in Array object makes Javascript a very expressive language, a virtual database with its flexible notation and rich palette of methods. Arrays allow all kinds and amounts of data to be searched, sorted and reversed with one single command. An array of arrays has all the magic of a multidimensional array. Together with the various loop mechanisms these jewels in code should make scripting for the millions a piece of cake. Whether loose or typed, dense or sparse, long live the Array.

The standard, and a solution

The exact maximum limit of an array is 2^32 - 1 or 4294967295, due to restrictions in Javascript's memory. The number of items, also known as the length property, cannot be greater than that. Other properties, namely the methods, are a matter of agreement: the ECMA 262-3 standard.

Unfortunately not all of the standard array functions are implemented in all browsers. Most notably IE5, still used by a consiberable group of people, lacks most of the basic functions found in the current Javascript, 1.5, which is present in Netscape 6 and in Internet Explorer 5.5 as JScript version 3. The array methods are in danger of getting a bad reputation because they cannot be used reliably on the wide open Internet.

However, new methods can be added through the prototype property, with a few well chosen instructions. This file array.js, loaded with any document, will provide the missing functions so older browsers can catch up to the full array functionality of ECMA262-3, if only for one page. To include it, copy the file to your server, or simply add this line in the document head:

<script src="http://4umi.com/web/javascript/array.js" type="text/javascript"></script>

It contains prototypes for all array methods, each wrapped in an if statement to avoid overwriting any that are natively supported. In fact, it is the same code as presented lower on this page, but in a compressed format to minimize download time. Studying the prototypes of the emulations is a highway towards creating and adding new custom behaviour to the array, which is what we have been doing.

New methods

The methods described by the ECMA262-3 standard, implemented in JavaScript 1.5 and JScript 3, but lacking in earlier browsers, that are added to the Array object by this script, are:

FunctionDescriptionJSJ
concatAdd one or more arrays, and return the new array. No array is modified.1.23
copyCopy all elements to a new array, by reference if possible, or by value.
popRemove and return the last element of an array.1.25
pushAdd one or more elements to the end of an array, return the new length.1.25.5
shiftRemove and return the first element.1.25.5
sliceCopy and return several elements. The original array is not modified.1.23
spliceRemove or replace several elements and return any deleted elements1.25.5
unshiftAdd an element to the beginning of an array, return the new length.1.25.5

The "JS" column lists the version in which the method is first implemented in JavaScript, the "J" column does the same for JScript. The methods to join, reverse and sort are JS since 1.1 and JScript3.

In addition, the following non-standard methods, some expected additions in the next release of the language, others presently exclusive to our site, are also added:

FunctionDescription
forEachApply a function to each element. The array is not modified.
indexOfReturn index of the first element that matches value.
insert()Return an array with a value inserted, without overwriting existing keys.
lastIndexOfReturn index of the last element that matches value.
randomReturn a random element, optionally excluding the first few elements.
shuffleRearrange the order of the elements in a disorderly fashion.
uniqueReturn an array where duplicate elements have been removed.
walk()Apply a function to each element, return the new array.

A discussion of these methods follows.

Each for all and all forEach()

In their version of Javascript 1.5, presented on developer-test.mozilla.org/, the Open Sourcers have added a new non-standard method for array objects, named forEach() and similar in behaviour to the php foreach() array function: it allows the execution of a regular function with every element of the array as its parameter.

In their implementation, two parameters can be passed: the first, the name of the function to execute, is required, the other is optional and provides a means to pass an object to have this refer to during each invocation of the callback function. If it is omitted, the global object associated with the callback function is used as usual. In the current emulation, only the first parameter, the function name, is used.

Consider this bare-bones example with an array myarray and a function count(), which clearly expects nothing but an ordinary string as its parameter...

function count( a ) {
 window.alert( '"' + a.toUpperCase() + '" has ' + a.length + ' letters.' );
}

var myarray = [ 'Tom', 'Dick', '', , 'Harry', 7 ];
// myarray.forEach( count );

Then compare the effects of these buttons and or these links alert myarray and count myarray, which are made up from nothing more than this HTML:

<input type="button" onclick="myarray.forEach( alert );" value="alert myarray"/>
and
<input type="button" onclick="myarray.forEach( count );" value="count myarray"/>
or these links
<a href="#alert" onclick="myarray.forEach( alert );return false;">alert myarray</a>
and
<a href="#count" onclick="myarray.forEach( count );return false;">count myarray</a>

Note that forEach() does not change the offered array in any way, in contrast to the walk() method discussed below. It only uses its elements for whatever the callback function uses its arguments for.

The last indexOf()

The indexOf() method of the array object returns the position in the array of the first element that matches the specified argument. The lastIndexOf() (with a capital I) does the same, but searches from the end and returns the index of the last matching element. If they return the same value, it follows that in the given array there is only one element that matches at all.

The names and the idea have been copied from the indexOf() method of strings. The returnvalue for unsuccessful searches is also the same: if the argument is not found in array, a value of -1 is returned. The elements are counted from 0 (zero), again like characters in a string.

alert( [ 'one', 'two', 3, 4 ].indexOf( 'one' ) );  //  0 (zero)/**/
alert( [ 'one', 'two', 3, 4 ].indexOf( 'One' ) );  //  -1, different case/**/
alert( [ 'one', 'two', 3, 4 ].indexOf( '4' ) );  //  -1, different type/**/
alert( [ 'one', 'two', 3, 4 ].indexOf( 4 ) );  //  3/**/
alert( [ 'one', 'two', 3, 4 ].lastIndexOf( 4 ) );  //  3/**/
alert( [ 'one', 'two', 3, 4, 4 ].lastIndexOf( 4 ) );  //  4/**/

Congruently to the indexOf() and lastIndexOf() methods of strings, an optional second parameter can be passed to specify the position to begin searching:

alert( [ 'one', 'two', 3, 4, 'one' ].indexOf( 'one', 1 ) );  //  4/**/
alert( [ 'one', 'two', 3, 4, 'one' ].lastIndexOf( 'one', 1 ) );  //  4/**/

Within strings, there are only substrings. A variable holding a single character as its value is just a string of length 1. But within an array, elements can be of different types. 4 is a number, '4' is a string, and [4] is an array itself, although in many cases, they will behave the same way in a script, and common sense confirms they are more or less the same thing. By default, indexOf() and lastIndexOf() only match elements that are of the same value and the same type, but a third parameter can be passed to indicate the search needs to match the value only:

alert( [ 'one', 'two', 3, 4 ].indexOf( '4', 0, true ) );  //  3/**/

A direct way to tell the functions to search from the beginning of an array upto a certain index is not provided natively. Use the reverse() method of arrays for that.

Some random() results

As the name suggests, the random() function takes an array and returns a random element. An optional parameter can be passed that will limit the choice of elements in the array. If the parameter is a positive number, only elements up to (and including) that index are considered. If the number is negative, only elements from that point onwards join the game.

var kids = [ 'Tom', 'Dick', 'Harry', [ 'Karen', 'Laura' ], 'Mary', 7, 8, 9 ];
alert( kids.random() );/**/
alert( kids.random( 3 ) );  //  returns a boy's name/**/
alert( kids.random( -5 ) );  //  returns a number/**/

As demonstrated in the given examples, the type of returned value makes no difference to the randomizer. Karen and Laura are not elements of the example array kids, but are contained within a subarray, which itself is one and only one distinct element in the example array, and may be returned as such.

Shake and shuffle()

Similarly to the above, the shuffle() function performs some pretty random actions on the given array, but then returns the whole array. The elements have been moved around and the order in which they appear in the array has been altered beyond recognition. This functionality is often asked for in games, but it may have its uses elsewhere too.

alert( [ 'Tom', 'Dick', 'Harry', [ 'Karen', 'Laura' ], 'Mary', 7, 8, 9 ].shuffle() );/**/

Note that Karen and Laura always appear together, no matter how often you try to scramble them apart. But the shuffling is done on the outer array only, so their relative position to eachother will never change.

Ha, there comes our optional parameter. It defaults to a boolean false, but will shuffle sub- and multisubarrays in their own right, if true. Note that it still does not mix the elements between the various arrays. You would need to tear them apart and feed the pieces separately for that.

var nums = [ 7, 8, 9 ],
 boys = [ 'Tom', 'Dick', 'Harry' ],
 girls = [ 'Iris', 'Juliet', [ 'Karen', 'Laura' ], 'Mary' ];
alert( [ boys, girls, nums ].shuffle( true ) );/**/

Don't try too hard.

At last a unique() method

Nothing is unique in that everything is unique. However, an array soiled with duplicate entries or multiple elements of the same value, which is run through this function, just may end up that utopian array where all elements are created equal, and none is more equal than the other:

var Dick = [ 'Dick' ];
alert( [ 'Tom', 'Dick', 'Harry', [ 'Tom' ], 'Dick', 'Harry', 'Tom', ' Dick ', Dick ].unique() );/**/
alert( [ 'Tom', 'Dick', 'Harry', [ 'Tom' ], 'Dick', 'Har'+'ry', 'tom', Dick ].unique( true ) );/**/

Guess you guessed it by now about the optional parameter that, when boolean true, allows for the different types of similar values to be discarded during comparison. Otherwise, the first occurance of any value will determine its final type and index in the array. To make it the last, reverse() the array first.

The examples were not chosen to reflect any plausible real-life situation, but rather more to demonstrate what are actually considered different types.

The code

Presented here is the code that adds the new methods as prototypes to the Array object. It is the contents of our aforementioned array.js file, with added comments and whitespace. (And between you and me, that file is not included or linked to on the current page: the examples on this page run this text.) Highlight it for some overview.

The variable a is usually used to represent an array, b a boolean value, f a function object, i and j iterate, l is a (constant) length and r something to return.

// -- Standard functions

// Array.concat() - Join two arrays
if( typeof Array.prototype.concat==='undefined' ) {
 Array.prototype.concat = function( a ) {
  for( var i = 0, b = this.copy(); i<a.length; i++ ) {
   b[b.length] = a[i];
  }
  return b;
  };
}

// Array.copy() - Copy an array
if( typeof Array.prototype.copy==='undefined' ) {
 Array.prototype.copy = function() {
  var a = [], i = this.length;
  while( i-- ) {
   a[i] = typeof this[i].copy!=='undefined' ? this[i].copy() : this[i];
  }
  return a;
 };
}

// Array.pop() - Remove and return the last element of an array
if( typeof Array.prototype.pop==='undefined' ) {
 Array.prototype.pop = function() {
  var b = this[this.length-1];
  this.length--;
  return b;
 };
}

// Array.push() - Add an element to the end of an array, return the new length
if( typeof Array.prototype.push==='undefined' ) {
 Array.prototype.push = function() {
  for( var i = 0, b = this.length, a = arguments, l = a.length; i<l; i++ ) {
   this[b+i] = a[i];
  }
  return this.length;
 };
}

// Array.shift() - Remove and return the first element
if( typeof Array.prototype.shift==='undefined' ) {
 Array.prototype.shift = function() {
  for( var i = 0, b = this[0], l = this.length-1; i<l; i++ ) {
   this[i] = this[i+1];
  }
  this.length--;
  return b;
 };
}

// Array.slice() - Copy and return several elements
if( typeof Array.prototype.slice==='undefined' ) {
 Array.prototype.slice = function( a, c ) {
  var i, l = this.length, r = [];
  if( !c ) { c = l; }
  if( c<0 ) { c = l + c; }
  if( a<0 ) { a = l - a; }
  if( c<a ) { i = a; a = c; c = i; }
  for( i = 0; i < c - a; i++ ) { r[i] = this[a+i]; }
  return r;
 };
}

// Array.splice() - Remove or replace several elements and return any deleted elements
if( typeof Array.prototype.splice==='undefined' ) {
 Array.prototype.splice = function( a, c ) {
  var i = 0, e = arguments, d = this.copy(), f = a, l = this.length;
  if( !c ) { c = l - a; }
  for( i; i < e.length - 2; i++ ) { this[a + i] = e[i + 2]; }
  for( a; a < l - c; a++ ) { this[a + e.length - 2] = d[a - c]; }
  this.length -= c - e.length + 2;
  return d.slice( f, f + c );
 };
}

// Array.unshift() - Add an element to the beginning of an array
if( typeof Array.prototype.unshift==='undefined' ) {
 Array.prototype.unshift = function() {
  this.reverse();
  var a = arguments, i = a.length;
  while(i--) { this.push(a[i]); }
  this.reverse();
  return this.length;
 };
}

// -- 4umi additional functions

// Array.forEach( function ) - Apply a function to each element
Array.prototype.forEach = function( f ) {
 var i = this.length, j, l = this.length;
 for( i=0; i<l; i++ ) { if( ( j = this[i] ) ) { f( j ); } }
};

// Array.indexOf( value, begin, strict ) - Return index of the first element that matches value
Array.prototype.indexOf = function( v, b, s ) {
 for( var i = +b || 0, l = this.length; i < l; i++ ) {
  if( this[i]===v || s && this[i]==v ) { return i; }
 }
 return -1;
};

// Array.insert( index, value ) - Insert value at index, without overwriting existing keys
Array.prototype.insert = function( i, v ) {
 if( i>=0 ) {
  var a = this.slice(), b = a.splice( i );
  a[i] = v;
  return a.concat( b );
 }
};

// Array.lastIndexOf( value, begin, strict ) - Return index of the last element that matches value
Array.prototype.lastIndexOf = function( v, b, s ) {
 b = +b || 0;
 var i = this.length; while(i-->b) {
  if( this[i]===v || s && this[i]==v ) { return i; }
 }
 return -1;
};

// Array.random( range ) - Return a random element, optionally up to or from range
Array.prototype.random = function( r ) {
 var i = 0, l = this.length;
 if( !r ) { r = this.length; }
 else if( r > 0 ) { r = r % l; }
 else { i = r; r = l + r % l; }
 return this[ Math.floor( r * Math.random() - i ) ];
};

// Array.shuffle( deep ) - Randomly interchange elements
Array.prototype.shuffle = function( b ) {
 var i = this.length, j, t;
 while( i ) {
  j = Math.floor( ( i-- ) * Math.random() );
  t = b && typeof this[i].shuffle!=='undefined' ? this[i].shuffle() : this[i];
  this[i] = this[j];
  this[j] = t;
 }
 return this;
};

// Array.unique( strict ) - Remove duplicate values
Array.prototype.unique = function( b ) {
 var a = [], i, l = this.length;
 for( i=0; i<l; i++ ) {
  if( a.indexOf( this[i], 0, b ) < 0 ) { a.push( this[i] ); }
 }
 return a;
};

// Array.walk() - Change each value according to a callback function
Array.prototype.walk = function( f ) {
 var a = [], i = this.length;
 while(i--) { a.push( f( this[i] ) ); }
 return a.reverse();
};

Reference