Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 9 of 9
  1. #1
    Master Coder felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, Australia
    Posts
    6,642
    Thanks
    0
    Thanked 649 Times in 639 Posts

    Maximum value in an array

    I have noticed a number of queries where someone needs to get the maximum (or sometimes the minimum) value that is contained in an array. Just about every solution offered involves multiple lines of code and a loop but there is actually a single JavaScript command that will retrieve the maximum value from an array directly with only the one statement and no loop being required.

    Let's start with an array of numbers where we need to get the maximum value.

    Code:
    ary = [4,7,-2,55,2,12];
    It doesn't really matter how we get those numbers into the array, all that matters is that we have an array and want to get the maximum value. To get the maximum value from the array we run the following code:

    Code:
    max = Math.max.apply(Math,ary);
    The variable max now contains the maximum value from the array called ary (if you run both these statements then max contains 55).

    To get the minimum value just substitute Math.min instead of Math.max

    No loops and just a single call and we have the answer.

    Where JavaScript provides a single call that gives the answer you should not try to reinvent the wheel by writing your own less efficient code to do the same thing. There are enough things that JavaScript doesn't provide single commands for that can be used instead in homework questions where a loop would be required (or whatever the question is intended to test) without asking questions that are better solved wothout using the construct that the question is supposed to be testing.
    Last edited by felgall; 11-19-2012 at 10:01 PM.
    Stephen
    Learn Modern JavaScript - http://javascriptexample.net/
    Helping others to solve their computer problem at http://www.felgall.com/

    Don't forget to start your JavaScript code with "use strict"; which makes it easier to find errors in your code.

  • #2
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,460
    Thanks
    11
    Thanked 600 Times in 580 Posts
    great point, that's very useful. i see a lot of wheel-re-invention around here.
    i don't see generics used enough.

    i personally think it's neat to overload the 20-something keywords and built-ins JS provides.

    the newer revisions in current browsers make recycling even easier and more dynamic.
    this was done by spec'ing most methods as generic.

    so, if you don't like typing ".apply" all the time, pre-bake an array-expecting version using bind:


    Code:
    var max=Math.max.apply.bind(Math.max, 0);
    now, the hot awesomeness works without the apply() making it handier to use and re-use.

    Code:
    var max=Math.max.apply.bind(Math.max, 0);
    
    var myMax= max([4,7,-2,55,2,12]) ;
    
    alert( myMax ); // shows 55
    if you need to support browsers going back a couple versions behind the current, a drop-in replacement will provide enough .bind() for this pattern to work.



    since we're here, i'll mention my other favorite bind, a slightly more complex snip that makes a callable that allows you create arrays from anything with a .length property, like String, domCollections, arguments, etc:

    Code:
    var arr=Function.prototype.apply.bind([].slice);
    
    var myArray= arr("abcde");
    alert(myArray); // shows: "a,b,c,d,e"


    how about getting all the even numbers from an array without a loop or using the word "return" ?

    Code:
    var isEven=/./.test.bind( /[-2468](\.|$)/ );
    
    alert(
     [1,2,3,4,5,6].filter( isEven  )  
    )   //shows: "2,4,6"



    ok, one more, last one i swear

    i hate typing "document.createElement(this), document.createElement(that), document.createElement(blah)" all the time, my poor fingers.

    Rescue my over-worked digits Function.bind():
    Code:
    var create=document.createElement.bind(document);
    
    var div=create("div");
     div.className=123;
     div.id=321;
     div.appendChild(create("hr"));
    
     alert(div.outerHTML); // shows: "<div class="123" id="321"><hr></div>"
    you can re-bind getElementsByTagName, querySelectorAll, and all those other long-winded dom methods into short callable functions.

    since the JS compiler has less of an activation envelope to create, a bound function runs 10-20% faster than an unbound one. this can almost erase the typical function call overhead of 18%, and your user functions say "[native code]" when you stringify them; very cool.


    @felgall - sorry about the hijacking. i was just going to mention .bind() and be on my way, but i obviously got carried away again. it happens, and i think this is good info and related to your idea, so i won't delete it.
    Last edited by rnd me; 11-20-2012 at 09:58 AM.
    my site (updated 2014/10/20)
    BROWSER STATS [% share] (2014/9/03) IE7:0.1, IE8:4.3, IE11:9.2, IE9:2.7, IE10:2.6, FF:16.8, CH:47.5, SF:7.8, NON-MOUSE:37%

  • #3
    Senior Coder Dormilich's Avatar
    Join Date
    Jan 2010
    Location
    Behind the Wall
    Posts
    3,472
    Thanks
    13
    Thanked 361 Times in 357 Posts
    @rnd_me: could you explain why var max=Math.max.apply.bind(Math.max, 0); requires a second parameter? (I tried various ones, it only matters that there is one)


    PS. John Resig claims responsibility for that "hack" to the Book "JavaScript, the definitive guide".
    The computer is always right. The computer is always right. The computer is always right. Take it from someone who has programmed for over ten years: not once has the computational mechanism of the machine malfunctioned.
    André Behrens, NY Times Software Developer

  • #4
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,460
    Thanks
    11
    Thanked 600 Times in 580 Posts
    Quote Originally Posted by Dormilich View Post
    @rnd_me: could you explain why var max=Math.max.apply.bind(Math.max, 0); requires a second parameter? (I tried various ones, it only matters that there is one)
    .
    a lot of folks, myself included, are confused at first by bind.

    my big "aha" moment of understanding came by lining it up with the other two Function.prototype methods we all use and love; call() and apply().


    turns out, bind() is EXACTLY like call() : it sets "this" with the first argument, and passes all other specified arguments as passed to the function you are calling it on. the only diff is that call() executes the function right there, whereas bind() stops short, returning the scoped function (the same as call would use) instead of the result.

    let's throw bind() out, and examine the issue with the 1999 pieces.

    you may have done something like this at some point:
    Code:
     Math.max.apply("something", [1,2,3,4])
    you need to use "something" there for the same reason as zero (or something else) in my code.

    apply's first argument is sucked into this.
    if all that was passed was the array, max() would be called with the array as this, and with no arguments. apply() wants an array of arguments as arguments[1] to turn into arguments on the applied function.

    the reason this works is because Math.max() doesn't look at this, it only looks at it's arguments, one number per argument:


    Code:
    Math.max.bind(0)([1,2,3]) //won't work
    Math.max.call(0)([1,2,3]) //won't work
    Math.max.call(0, [1,2,3]) // won't work
    Math.max.call(0 ,1,2,3) // works
    Math.max.apply(0, [1,2,3]) // works
    Math.max.apply(999, [1,2,3]) // works
    less obvious in my code is what's being bound.
    it's not max(), it's apply().

    check this out:
    Code:
    max=String.apply.bind(Math.max, 0);
    max([1,2,3]) // ===3
    yeah, that's weird, but it works because apply() doesn't care what is in front or behind it. it's a generic. i guess you can think of it as being "loose"; it doesn't care what function, this, or arguments it's hooked up with. heh.


    the apply() generic steps this out for greater detail:

    Code:
    apply=Function.prototype.apply; //the template for "all applies"
    max=apply.bind(Math.max, 0); //bind apply to max
    max([1,2,3]); //call apply, bound to max, on argument[0]
    apply() wants two args: a new this and an array of other arguments.
    bind wants 1+ arguments: this and others to pre-bake.

    thus, we are essentially stacking the args for apply onto the args for max().

    again, we can break it down to zoom in.

    the apply() method can be (jankily) written in userland using call:

    Code:
    function apply2(that,a){ 
      var args=JSON.stringify(a).trim().slice(1,-1);
      var code= "this.call(that," + args + ")"  ;
    
    alert([
        "APPLYING", 
        "arguments: "+[].slice.call(arguments), 
        "call code: "+code, 
        "this: "+ this, 
        "args: "+args, 
        "that: "+that
    ].join("\n"));
    
       return eval( code );
     }//end apply2()
    
    //go ahead and make it work side-by-side with the "real" fn.apply():
    Function.prototype.apply2=apply2;
    
    //try it as a function method:
    Math.max.apply2(0, [1,2,3]);  //works
    
    //try it as a generic:
    apply2.call(Math.max, 0, [1,2,3]);  //works
    
    //try it as a bound method to turn arguments into an array:
    apply2.bind(Math.max, 0)([1,2,3]) // works


    hmm, that a bit of typing i just did, doh!
    im tryin to stop my mic-hogging.
    you can "alert-ify" the Function.prototype.bind replacement on the MDC page i linked to if you want to step-through bind() like the apply2 i made above.

    does that help, or did i cloud the issue further?
    Last edited by rnd me; 11-20-2012 at 06:30 PM.
    my site (updated 2014/10/20)
    BROWSER STATS [% share] (2014/9/03) IE7:0.1, IE8:4.3, IE11:9.2, IE9:2.7, IE10:2.6, FF:16.8, CH:47.5, SF:7.8, NON-MOUSE:37%

  • #5
    Senior Coder Dormilich's Avatar
    Join Date
    Jan 2010
    Location
    Behind the Wall
    Posts
    3,472
    Thanks
    13
    Thanked 361 Times in 357 Posts
    Quote Originally Posted by rnd me View Post
    less obvious in my code is what's being bound.
    it's not max(), it's apply().

    check this out:
    Code:
    max=String.apply.bind(Math.max, 0);
    max([1,2,3]) // ===3
    yeah, that's weird, but it works because apply() doesn't care what is in front or behind it. it's a generic. i guess you can think of it as being "loose"; it doesn't care what function, this, or arguments it's hooked up with. heh.
    it made "click" here. what I didn’t see is that the Math.max in front of apply.bind() is just the source for apply() and hence the first parameter is the this for apply() and the second the this for bind() (which is irrelevant as there is no this in a static function).
    The computer is always right. The computer is always right. The computer is always right. Take it from someone who has programmed for over ten years: not once has the computational mechanism of the machine malfunctioned.
    André Behrens, NY Times Software Developer

  • #6
    Master Coder felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, Australia
    Posts
    6,642
    Thanks
    0
    Thanked 649 Times in 639 Posts
    Quote Originally Posted by rnd me View Post
    @felgall - sorry about the hijacking. i was just going to mention .bind() and be on my way, but i obviously got carried away again. it happens, and i think this is good info and related to your idea, so i won't delete it.
    I don't see how you hijacked the thread with your suggestion. As you said it is good info and definitely related to what I posted.

    The part you didn't cover is why call and apply (and bind) have that first parameter. The original intention for those Function methods is to allow a method attached to one object to be borrowed by a different object. The first parameter is the object that is borrowing the method from the one that is being called in the code. It just happens that in each of the examples that we have posted here that we are borrowing the method to use as a function rather than as a method attached to a different object. Since the code we are calling makes no reference to "this" it doesn't care what is in that first parameter. Where the function being called - max() in my example - doesn't contain any references to the "this" it doesn't matter what you place in the first parameter as the object that the non-existent "this" references are to refer to. The advantage apply() has in situations like in my original post is that it takes care of converting an array into an arguments list for you in the same way that var args = Array.prototype.slice.call(arguments); converts arguments into an array. (you can of course shorten both ways using bind).

    You can also use apply when you have nested functions and want to pass the arguments passed to the outer function directly into the inner function.

    Example 8.5 on page 189 of the book "JavaScript: The Definitive Guide" 6th Edition has code to add Function.prototype.bind for those browsers that don't support it natively. There are a couple of minor differences that the text on the page lists but the supplied code ought to allow most of the uses for bind() that you can think of in the older browsers - so there is no reason to avoid using it just because some of your visitors are using those older browsers.

    Just one extra point - If you use 0 as the second parameter in the bind for Math.max.apply then you eliminate the possibility of the maximum being negative. That second parameter should be Number.NEGATIVE_INFINITY in order to allow the maximum to be negative.
    Last edited by felgall; 11-21-2012 at 09:59 AM.
    Stephen
    Learn Modern JavaScript - http://javascriptexample.net/
    Helping others to solve their computer problem at http://www.felgall.com/

    Don't forget to start your JavaScript code with "use strict"; which makes it easier to find errors in your code.

  • #7
    Master Coder felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, Australia
    Posts
    6,642
    Thanks
    0
    Thanked 649 Times in 639 Posts
    Here's a shorter version of bind than the one I mentioned from the book - it gets rid of the unnecessary loops.

    Code:
    if (!Function.prototype.bind) {
       Function.prototype.bind = function(o) {
       var self, bndargs;
       self = this;
       bndargs = Array.prototype.slice.call(arguments);
       bndargs.shift();
       return function() {
          var args = bndargs.concat(Array.prototype.slice.call(arguments));
          return self.apply(o, args);
          };
       };
    };
    Stephen
    Learn Modern JavaScript - http://javascriptexample.net/
    Helping others to solve their computer problem at http://www.felgall.com/

    Don't forget to start your JavaScript code with "use strict"; which makes it easier to find errors in your code.

  • #8
    Senior Coder rnd me's Avatar
    Join Date
    Jun 2007
    Location
    Urbana
    Posts
    4,460
    Thanks
    11
    Thanked 600 Times in 580 Posts
    Quote Originally Posted by felgall View Post
    Just one extra point - If you use 0 as the second parameter in the bind for Math.max.apply then you eliminate the possibility of the maximum being negative. That second parameter should be Number.NEGATIVE_INFINITY in order to allow the maximum to be negative.
    aw man, you shoulda stopped while you're ahead; you got the ball rollin on this. i did cover the 2nd arg implications in my follow-up to Dormilich's note. or, i should say, i tried...

    but, for the sake of accurate info for noobs reading this thread, i must point out that you are dead wrong about this particular point.

    the 2nd param to bind (in the case of Math.max) is ignored completely, so negatives work using 0 as well as false, {}, or [/rx/]; anything goes except nothing.


    proof is easy, just use set of negatives to test your hypothesis:

    Code:
    var max=Math.max.apply.bind(Math.max, 0);
    var myMax= max([-4,-7,-2,-55,-2,-12]) ;
    
    alert( myMax ); // shows "-2"
    -2 is the largest numeral in the set, so this code immediately falsifies your hypothesis that the use of "0 as the second parameter in the bind for Math.max.apply ... eliminate(s) the possibility of the maximum being negative" (emphasis mine).

    try something less confusing; using garbage for this, and a different apply:

    Code:
    var max=isNaN.apply.bind(Math.max, {a:1});
    var myMax= max([-4,-7,-2,-55,-2,-12]) ;
    
    alert( myMax ); // shows "-2"
    still works. if a function doesn't use this, as Math.max doesn't, this doesn't matter.
    Last edited by rnd me; 11-21-2012 at 10:17 AM.
    my site (updated 2014/10/20)
    BROWSER STATS [% share] (2014/9/03) IE7:0.1, IE8:4.3, IE11:9.2, IE9:2.7, IE10:2.6, FF:16.8, CH:47.5, SF:7.8, NON-MOUSE:37%

  • #9
    Master Coder felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, Australia
    Posts
    6,642
    Thanks
    0
    Thanked 649 Times in 639 Posts
    Quote Originally Posted by rnd me View Post
    aw man, you shoulda stopped while you're ahead; you got the ball rollin on this. i did cover the 2nd arg implications in my follow-up to Dormilich's note. or, i should say, i tried...

    but, for the sake of accurate info for noobs reading this thread, i must point out that you are dead wrong about this particular point.
    Strange because I tested it prior to adding that comment to the end of my post and only added the comment because in the browser I was testing in the value that parameter did make a difference.

    The test I did was with my original array with the - sign removed and replacing max with min - and then with the 0 as the second parameter I got the 0 returned instead of the 2 only by changing the 0 to a number bigger than the minimum in the array could I get it to return the right answer. The first parameter to the bind substitutes for 'this' but the second parameter simply got added to the array.

    Obviously the implementation of bind varies slightly between browsers. Had my test returned the same result that your's did then I would not have gone back and amended the post to add that sentence. The difference probably relates to the number of objects that the browser considers to be involved that need to have a value supplied for 'this'.
    Last edited by felgall; 11-21-2012 at 08:54 PM.
    Stephen
    Learn Modern JavaScript - http://javascriptexample.net/
    Helping others to solve their computer problem at http://www.felgall.com/

    Don't forget to start your JavaScript code with "use strict"; which makes it easier to find errors in your code.


  •  

    Posting Permissions

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