JS Puzzle Solution

Published date: Oct 09, 2008

Write a function getMax(a,b) that takes two integers and returns the maximum value. None of the following are allowed: >, <, ==, !=, Math.max, Math.min.

We have previously seen how Javascript variables are interpreted. This time we will play a little bit with the Javascript syntax to go through some common and basic quirks of the language.

Note: Each snippet credits whoever was the first to send me that solution.

First Part: The Quick Answer

The first and most common answer I have received is the one which subtracts the two arguments and checks whether or not the minus sign is present in the result. Below you can see three different ways of doing the same with indexOf, test and match.

// By Neil Fraser
function getMax(a, b) {
 var diff = a - b;
 return (String(diff).indexOf('-') + 1) ? b : a;
}
// By Kangax
function getMax(a, b) {
  if (/^-/.test(((a-b)+''))) return b;
  return a;
};
// By Michael Johnston
function getMax(a, b) {
  return (b-a).toString().match('-') ? a : b;
}

Surprisingly, each of them also chose a different way to convert the numeric result to a string with the String constructor, + operator or the toString method.

My favorite solution though is one which also utilizes some math skills:

// By Neil Fraser
function getMax(a, b) {
  var diff = a - b;
  return (diff - Math.abs(diff)) ? b : a;
}
// Example with 3 and 7 as input values
diff = 3 - 7 = -4
return (-4 - Math.abs(-4)) ? 3 : 7;
==> return (-8) ? 7 : 3; // it will return 7 

// Example with 7 and 3 as input values
diff = 7 - 3 = 4
return (4 - Math.abs(4)) ? 3 : 7;
==> return (0) ? 3 : 7; // it will return 7 

Only in the cases where the subtrahend is the greatest number will the difference of the two numbers minus its absolute value be 0.

Second Part: No Conditionals

The second part of the problem is thinking of a solution which doesn't use any kind of conditionals such as if, switch of the ternary operator (?:). I didn't mention that in the original question as I had planned to ask for it later. However, almost everyone sent me a second solution "the day after" as a self initiative. It's in these solutions where you can see the most creative answers.

The + prefix

As we can see in the first part, both if and ternary operators conditionals are used to return one of the arguments depending on whether the evaluated statement is true or false.

Since the two arguments can be also accessed using the arguments array (arguments[0], arguments[1]) we only need to find a way to convert true -> 1 and false -> 0.

This can be accomplished with the + sign as a prefix which is an alias for the Number constructor:

// Michael Geary
function getMax( a, b ) {
  return arguments[ +!/-/.test( b - a ) ];
}

Which can be written as:

function getMax( a, b ) {
  return arguments[ Number(!/-/.test( b - a ))];
}
+true -> 1  // same as Number(true)

One of the uses of the + prefix I have seen recently is to get the timestamp.

+new Date() -> timestamp  /* same as new Date().getTime()
and same as Number(new Date()) */

A much more familiar prefix is the one that aliases the Boolean constructor: !!

!!(0) // returns false

Logic Operators

This is a clever combination of logic operators in order to create the equivalent of an if else conditional.

// By Neil Fraser
function getMax(a, b) {
  var diff = a - b;
  return ((diff - Math.abs(diff)) && b) || a;
}

Other common uses of those logic operators are:

// guard operator
var len = a && a.length; /* in order to avoid an error in case
that a doesn't have a value */
// default operator
var amount = n || 20; // if n doesn't have a value then use 20

Sort Method

// By Karol
function getMax(a,b){
  return [a,b].sort(function(c,d){return d-c;})[0];;
}

Karol's solution uses one of the properties of the sort method which allows you to pass a function as an argument so you can modify the condition used to sort the items. Internally the function will evaluate if the result of the operation is greater, less than or equals to 0 and it will sort the values accordingly.

This technique can also be used to sort numbers according to their value:

// default sort
[20,15,10,5,1].sort()  // 1,10,15,20,5
// custom sort
[20,15,10,5,1].sort(...)  // 1,5,10,15,20

More Math Skills

// By Karol
function getMax(a,b) {
 max=isFinite(Math.sqrt(a-b))*a+isFinite(Math.sqrt(b-a))*b;
 return max;
}

I love this one. How many times have you used the isFinite function before? I didn't even remember that it existed.

// Example with 6 and 15 as input values
isFinite(Math.sqrt(6-15))*6 - isFinite(Math.sqrt(15-6))*15
==> isFinite(Math.sqrt(-9))*6 - isFinite(Math.sqrt(9))*15
==> isFinite(NaN)*6 - isFinite(3)*15
==> false*6 - true*15
==> 0 - 15

The square root of a negative number will return NaN which will make the isFinite function return false. False multiplied by any number returns 0 whereas in the true case it'll return the multiplier, which is the maximum number.

Unfortunately, as much as it is a beautiful solution, it's also the only one which is not stable when input values are the same.

Finally, the solution I had in mind when I proposed the problem uses a couple of the techniques we have seen before.

// By Ernest Delgado
function getMax(a,b) { 
  return arguments[+!((a-b) + Math.abs(a-b))];
}

Why I played with that

It's not that I lost my mind playing with the JS language all day but for the last months I have been more and more focused on using the Yahoo! Front End best practices for exceptional web performance. And since Steve moved to Google they might be the ones that Google follow now too. [citation needed]

Besides all those Front End practices there are some language specific tricks you can apply to your Javascript code in order to improve the overall performance. Everyone knows that the traditional for loop:

for (var i = 0; i < sthg.length; i++) { ... }

Should be written as:

for (var i = 0, len = sthg.length; i < len; i++) { ... }

Especially if that 'sthg' is an element in the DOM tree.

I am sure that you have seen in some other people's code the use of ++i instead of i++. But, does it really make any difference here? Have you ever wondered which one of these is faster?:

i++, ++i, i += 1, i = i + 1, ~i * (-1)

(being the last one the one I found while playing with the bitwise operators). These questions will be addressed in a future post.

Many of these things will make a difference of the order of milliseconds and many times it will be meaningless. However, in a big company, having millions of users visiting our websites is a big deal. If we spend a considerable amount of resources on CDN's, SQL query optimizations, clustering storage systems and such to improve the performance some fractions of a second, why shouldn't we do the same on our very front end for the same purpose?

Go Back To Main Page

Update: Neil Fraser, who is a very dangerous guy in front of any kind of challenge, pointed out two more things:

The first one is regarding the optimal for loops. He gives the following possibility:

for(var i = 0, el; el = sthg[i]; i++) { ... }

"That's even faster since .length is never computed. It doesn't work in general as a loop, since if the array has a null, 0, '' or other false value it bails. This is specific to node lists."

The second thing he pointed out is that he found out that all the solutions mentioned in this page use the (a-b) operation at some point, so he created a more elaborate solution where he uses the input values as indexes of an array.

Update 2: Michael Geary sent me some more cool variations of the for loop:

for( var i = -1, item;  item = items[++i]; ) { ... }

for( var i = items.length, item;  item = items[--i]; ) { ... }

for( var i = 0, item;  item = items[i++]; ) { ... }

Regarding Neil Fraser's for loop for node lists he says:

"It also works perfectly well with an array of objects where the objects are known to be not falsy - a very common and useful case."

Go Back To Main Page