SitePoint Sponsor

User Tag List

Results 1 to 21 of 21
  1. #1
    Grumpy Mole Man Skunk's Avatar
    Join Date
    Jan 2001
    Location
    Lawrence, Kansas
    Posts
    2,067
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Improving the Array class

    One of my favourite things about Javascript is the way it allows you to add extra methods on to classes (or colections of objects) after they have been defined - including objects provided by Javascript itself! Here are a couple of simple improvements to the Array class that I put together today to help solve a problem:

    Code:
    /* Finds the index of the first occurence of item in the array, or -1 if not found */
    Array.prototype.indexOf = function(item) {
        for (var i = 0; i < this.length; i++) {
            if (this[i] == item) {
                return i;
            }
        }
        return -1;
    };
    
    /* Returns an array of items judged 'true' by the passed in test function */
    Array.prototype.filter = function(test) {
        var matches = [];
        for (var i = 0; i < this.length; i++) {
            if (test(this[i])) {
                matches[matches.length] = this[i];
            }
        }
        return matches;
    };
    
    /* Adds an item on to the end of an array */
    Array.prototype.append = function(item) {
        this[this.length] = item;
    };
    
    var a = new Array();
    a.append('item 1');
    a.append('item 2');
    
    itemsWith2InThem = a.filter(function(item) { return item.indexOf('2') > -1 });
    
    // itemsWith2InThem is now an array containing 'item 2'
    Has anyone else played around with this capability? Got any code snippets to share?

  2. #2
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Your append method already exists in recent javascript versions, but it's called push. It operates a bit differently than your method, though.
    Code:
    if ( typeof Array.push == 'undefined' )
    Array.prototype.push = function()
    {
    	for ( var i = 0; i < arguments.length; i++ )
    	{
    		this[this.length] = arguments[i];
    	}
    	return this.length;
    }
    As you can see, it takes an indefinite number of values, and returns the new length of the array.

    Here's some that I've written and used
    Code:
    /* Return new array with specified index removed
    ------------------------------------------- */
    Array.prototype.remove = function( indicies )
    {
    	if ( indicies == -1 ) return this;
    	if ( typeof indicies != 'object' ) indicies = [indicies];
    	var arr = new Array();
    	for ( var i = 0; i < this.length; i++ )
    	{
    		if ( indicies.indexOf( i ) != -1 ) continue;
    		arr.push( this[i] );
    	}
    	return arr;
    }
     
    /*	Apply method to each element of array
    ------------------------------------------- */
    Array.prototype.applyMethod = function( method, mutate )
    {
    	var arr = ( mutate ) ? this : new Array();
    	for ( var i = 0; i < this.length; i++ )
    	{
    		if ( typeof this[i][method] != 'undefined' ) arr[i] = this[i][method]();
    	}
    	if ( !mutate ) return arr;
    }
     
    /*	Remove duplicate values
    ------------------------------------------- */
    Array.prototype.removeDupes = function()
    {
    	var found = new Array();
    	for ( var i = 0; i < this.length; i++ )
    	{
    		if ( found.indexOf( this[i] ) == -1 ) found.push( this[i] );
    	}
    	return found;
    }
     
    /*	Return copy of array
    ------------------------------------------- */
    Array.prototype.copy = function()
    {
    	var copy = [];
    	for( var i = 0; i < this.length; i++ )
    	{
    		copy[i] = ( typeof this[i].copy != 'undefined' )?
    			this[i].copy():
    			this[i];
    	}
    	return copy;
    }
     
    /*	Swaps the values of two indicies
    ------------------------------------------- */
    Array.prototype.swap = function( index1, index2 )
    {
    	var temp = this[index1];
    	this[index1] = this[index2];
    	this[index2] = temp;
    }
    As you can see, some of them rely on the presence of others. I've written more specific ones for certain projects, but they wouldn't be of much use here.

    I've got extensions for Object, Number, and String, too. A great application for the applyMethod method I wrote above would be something like this
    Code:
    /* Trims leading and trailing whitespace from string
    ------------------------------------------- */
    String.prototype.trim = function()
    {
    	return this.replace( /^\s+|\s+$/g, "" );
    }
     
     
    var arr = [' one', 'two ', ' three '];
     
    arr.applyMethod( 'trim', true );
    Last edited by beetle; Apr 21, 2004 at 10:37.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  3. #3
    I'll take mine raw silver trophy MikeFoster's Avatar
    Join Date
    Dec 2002
    Location
    Alabama, USA
    Posts
    2,560
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Very useful stuff!

    Thanks, guys!

  4. #4
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'd like to comment that if you are going to include these in a script, you should document their namespace usage, similar to what I've done here, or document it in-script.

    It will (should) help developers when putting scripts together to track down namespace conflicts.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  5. #5
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    A couple more
    Code:
    /*	Randomly shuffles array (mutates)
    ------------------------------------------- */
    Array.prototype.shuffle = function()
    {
    	for ( var i = 0; i < this.length; i++ )
    	{
    		ind1 = Math.floor( Math.random() * this.length );
    		ind2 = Math.floor( Math.random() * this.length );
    		this.swap( ind1, ind2 );
    	}
    }
    /*	Reverse sorts the array
    ------------------------------------------- */
    Array.prototype.rsort = function()
    {
    	return this.sort( function( a, b )
    	{
    		if ( a == b ) return 0;
    		return ( a > b ) ? -1 : 1;
    	} );
    }
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  6. #6
    Grumpy Mole Man Skunk's Avatar
    Join Date
    Jan 2001
    Location
    Lawrence, Kansas
    Posts
    2,067
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I love the way the push method takes multiple arguments - that's really neat. I prefer 'append' as a method name because I can never remember if 'push' adds things to the beginning or end of an array ('append' is used by Python) but that's just a personal preference - since push is part of the javascript spec it probably makes more sense to use that.

  7. #7
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ya, and PHP uses the push syntax as well.

    It's probably a C++ thing, since both Javascript and PHP are C-based scripting languages.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  8. #8
    Under Construction Poop_Shoot's Avatar
    Join Date
    Jul 2003
    Location
    Sacramento, CA
    Posts
    330
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    They are funny names push, pop, shift, unshift!

  9. #9
    SitePoint Enthusiast
    Join Date
    May 2003
    Location
    Poland
    Posts
    89
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    beetle: your trim() does not work correctly, doesn't trim from the right

    This works correctly:
    Code:
    String.prototype.trim = function() {
        var s = this.replace(/^\s*/, "");
        return s.replace(/\s*$/, "");
    }

  10. #10
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Oops, I missed the global modifier. This will work
    Code:
    String.prototype.trim = function()
    {
    	return this.replace( /^\s+|\s+$/g, "" );
    }
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  11. #11
    SitePoint Enthusiast
    Join Date
    May 2003
    Location
    Poland
    Posts
    89
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Some other methods:

    Code:
    /* Check whether array contains given string */
    Array.prototype.contains = function(s) {
        var found = false;
        for (var i = 0; i < this.length; i++) {
            if (this[i] == s) {
                found = true;
                break;
            }
        }
        return found;
    }
    
    /* Indicates whether some other array is "equal to" this one */
    Array.prototype.equals = function(a) {
        if (this.length != a.length) {
            return false;
        }
        for (var i = 0; i < this.length; i++) {
            if (this[i] != a[i]) {
                return false;
            }
        }
        return true;
    }

  12. #12
    SitePoint Addict
    Join Date
    Nov 2003
    Location
    Malmoe, Sweden
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Somewhat of topic... *sorry about that*

    Read a couple of articles on this subject and I found a comment on the article here at Sitepoint, that you should be able to extend the document object doing something like:

    document.prototype.getElementsByAttributeValue = function(s) { ... }

    But I thought that prototype was non-existing under document and if I try I get "document.prototype is undefined". Whatīs wrong?

  13. #13
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm guessing you're using IE. IE doesn't extend the JS object model onto the DOM. Mozilla (and others) do extend things like the prototype property onto DOM nodes.

    However, in reality, 99% (or more) of the time you'll only be dealing with 1 document object, so adding members via prototype is largely over-doing it. Just do

    document.getElementsByAttributeValue = function(s) { ... }

    instead.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  14. #14
    SitePoint Addict
    Join Date
    Nov 2003
    Location
    Malmoe, Sweden
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Actually I got the error on FF 0.8.

    Yes, I noticed that I could do as you describe without prototype for about 2 minutes ago. When I was finished scripting I checked my mail and found your answer. What a coincidence

  15. #15
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ya, now that you mention it, the object you must extend to add members to all document instances isn't document, but Document or DocumentNode or DocumentObject. I don't really remember, since I never use it.
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  16. #16
    SitePoint Addict
    Join Date
    Nov 2003
    Location
    Malmoe, Sweden
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, well. Thanks a lot anyway.

  17. #17
    SitePoint Addict
    Join Date
    Nov 2003
    Location
    Malmoe, Sweden
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    After a few futile attempts I finally came up with the code below. I often find my self during development writing loops in loops to alert the contents of an array or multidimensional arrays only to see if itīs still what I expected (you know real old funky alert development). This extension work the same way that the print_r function in PHP (if you are familiar with that one) with the exception that it only handles arrays (doh!). I have not yet solved the case with associative arrays but Iīm working on it. Maybe you guys have some input on this matter? I bet there are 1000 ways to do this better and if you know one, do not hesitate to enlighten (donīt know if thatīs a word actually) me.
    Code:
    Array.prototype.print_r = function()
    {	
    	var strWhiteSpace = (arguments.length > 0 ? arguments[0] : '') + unescape('%A0') + ' ' + unescape('%A0');
    	
    	var strOutput = 'Array\n' + (arguments.length > 0 ? arguments[0] : '') + '(\n';
    	for (var n = 0, len = this.length; n < len; n++)
    	{
    		strOutput += strWhiteSpace + '[' + n + '] => ';
    		if (typeof(this[n]) == 'object')
    		{
    			strOutput += this[n].print_r(strWhiteSpace);
    		}
    		else
    		{
    			strOutput += this[n];
    		}
    		strOutput += '\n';
    	}
    	strOutput += (arguments.length > 0 ? arguments[0] : '') + ')';
    	
    	return strOutput;
    }
    Edit:

    Iīm sure it could be made more compact with a little ?: syntax (ternary operator) but it will do for now.


    Syntax:
    Code:
    var arrArray = new Array(3, 2, 7, 9, 1, new Array('a', 'd', new Array('Foo', 'Bar', ' is good'), 'b'), 6, 8, 4, 5);
    alert(arrArray.print_r());

  18. #18
    Web-coding NINJA! silver trophy beetle's Avatar
    Join Date
    Jul 2002
    Location
    Dallas, TX
    Posts
    2,900
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sure, try this on for size

    debug.js
    Code:
    Array.prototype.print_r = function()
    { 
    	document.write( this.getDebugString() );
    }
     
    Array.prototype.alert_r = function()
    {
    	alert( this.getDebugString() );
    }
     
    Array.prototype.getDebugString = function( numTabs )
    {
    	// We subract one from numTabs here and add two later because of an odd illegal radix error
    	numTabs	 = ( typeof numTabs == 'undefined' ) ? 0 : numTabs - 1;
    	var tabs	= ('\t').replicate( numTabs );
    	var output = 'Array\n' + tabs + '(\n';
    	for ( var i = 0, l = this.length; i < l; i++ )
    	{
    		output += tabs + '\t[' + i + '] => ' + this[i].getDebugString( numTabs + 2 ) + '\n';
    	}
    	return output + tabs + ')';
    }
     
    Object.prototype.getDebugString = function( numTabs )
    {
    	// We subract one from numTabs here and add two later because of an odd illegal radix error
    	numTabs	 = ( typeof numTabs == 'undefined' ) ? 0 : numTabs - 1;
    	var tabs	= ('\t').replicate( numTabs );
    	var output = 'Object\n' + tabs + '{\n';
    	for ( var i in this )
    	{
    		output += tabs + '\t[' + i + '] => ' + this[i].getDebugString( numTabs + 2 ) + '\n';
    	}
    	return output + tabs + '}';
    }
     
    Function.prototype.getDebugString = function()
    {
    	return 'function() { ... }';
    }
     
    String.prototype.getDebugString	 = String.prototype.toString;
    Number.prototype.getDebugString	 = Number.prototype.toString;
    Boolean.prototype.getDebugString	= Boolean.prototype.toString;
     
    // helper method
    String.prototype.replicate = function( qty )
    {
    	var output = '';
    	for ( var i = 0; i < qty; i++ )
    	{
    		output += this;
    	}
    	return output;
    }
    And usage
    Code:
    <pre>
    <script type="text/javascript">
    	var arrArray =
    	[
    		3,
    		2,
    		7,
    		9,
    		1,
    		[
    			'a',
    			'd',
    			[
    				'Foo',
    				'Bar',
    				' is good'
    			],
    			'b'
    		],
    		6,
    		{
    			member1: 'blah',
    			member2: 'yadda'
    		},
    		4,
    		5
    	];
    	arrArray.print_r();
    	arrArray.alert_r();
     
    </script>
    </pre>
    beetle a.k.a. Peter Bailey
    blogs: php | prophp | security | design | zen | software
    refs: dhtml | gecko | prototype | phpdocs | unicode | charsets
    tools: ide | ftp | regex | ffdev




  19. #19
    SitePoint Addict
    Join Date
    Nov 2003
    Location
    Malmoe, Sweden
    Posts
    265
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Sweet :-) I didnīt get the \t to work i my alerts so I used the escape('%A0'). Sure has a lot to learn...

  20. #20
    SitePoint Enthusiast
    Join Date
    May 2003
    Location
    Poland
    Posts
    89
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've written tests to all functions, so you are sure that they work correctly:

    Code:
    <pre><script type="text/javascript" src="Array.js"></script>
    <script type="text/javascript">
    
        function test(b, s) {
            if (typeof b == "boolean") {
                if (b) document.writeln(s + " success");
                else document.writeln("<font color='red'>" + s + " failed" + "</font>");
            } else {
                document.writeln("<font color='red'>test() failed: " + s + "</font>");
            }
        }
    
        /*
        test(true, "test(true)"); // ok
        test(false, "test(false)"); // failed
        test(1, "test(1)"); // failed
        */
    
        /* Array.contains() */
    
        var a = ["a", "b", "c"];
        test(a.contains("c"), "Array.contains() - Test 1");
        test(!a.contains("d"), "Array.contains() - Test 2");
    
        /* Array.equals() */
    
        var b1 = ["a", 12, "some"];
        var b2 = ["a", "12", "some"];
        var b3 = ["a", 12, "some"];
        test(!b1.equals(b2), "Array.equals() - Test 1");
        test(b1.equals(b3), "Array.equals() - Test 2");
        
        /* Array.indexOf() */
    
        var c1 = ["a", "b", "c", "12"];
        test(c1.indexOf("b") == 1, "Array.indexOf() - Test 1");
        test(c1.indexOf(12) == -1, "Array.indexOf() - Test 2");
    
        /* Array.filter() */
    
        var d1 = [5, 7, 12, 18, 23];
        var d2 = [12, 18, 23];
        function testFilter(i) {
            return i > 10;
        }
        var d3 = d1.filter(testFilter);
        test(d2.equals(d3), "Array.filter() - Test 1");
    
        /* Array.applyFunc() */
    
        var e1 = [11, 13, 15, 17];
        var e2 = [9, 11, 13, 15];
        function testApplyFunc(i) {
            return i - 2;
        }
        e1.applyFunc(testApplyFunc);
        test(e1.equals(e2), "Array.applyFunc() - Test 1");
    
        /* Array.removeByIndex() */
        var f1 = ["g", "h", "j"];
        var f2 = ["g", "j"];
        f1.removeByIndex(1);
        test(f1.equals(f2), "Array.removeByIndex - Test 1");
    
        /* Array.removeByValue() */
        var g1 = ["a", "b", "c", "i", "c", "e"];
        var g2 = ["a", "b", "i", "e"];
        g1.removeByValue("c");
        test(g1.equals(g2), "Array.removeByValue() - Test 1");
    
        /* Array.removeDuplicates() */
    
        var h1 = ["a", 55, "q", "s", "q", 1, 55, "some"];
        var h2 = ["a", 55, "q", "s", 1, "some"];
        h1.removeDuplicates();
        test(h1.equals(h2), "Array.removeDuplicates() - Test 1");
    
        /* Array.copy() */
    
        var i1 = ["test", 100, "g"];
        var i2 = i1.copy();
        test(i1.equals(i2), "Array.copy() - Test 1");
    
        /* Array.swap() */
    
        var j1 = ["a", "b", "c"];
        var j2 = ["a", "c", "b"];
        j1.swap(1, 2);
        test(j1.equals(j2), "Array.swap() - Test 1");
    
        /* Array.shuffle() ??? */
    
        var k1 = [];
    
    </script></pre>
    Some of the functions in this topic are unnecessary, for example: rsort(). There is a native reverse() method, which works the same.
    I've modified some of the functions (removeDuplicates() mutates itself, indexOf() didn't check type of values, so 1 == true or 12 == "12".), added new, written tests for them. All can be downloaded here: Array.zip

    Btw. how to write a test for shuffle() that will always work ?

  21. #21
    SitePoint Member
    Join Date
    Apr 2005
    Posts
    1
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by cagrET
    Btw. how to write a test for shuffle() that will always work ?
    How about simply iterating through the shuffled array and checking that the set of elements is exactly the same as those in the original array?

    (sorry for the late reply... I just thought I'd throw in my $0.02 in case it makes any difference)


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •