Maximum function challenge

@mtf I’ve finally got round to looking at your function challenge :wink:

This function always returns the higher/maximum of 2 numbers…
EXCEPT when A is 0 (falsy), B is a truthy number, and A is greater than B (i.e. B is negative)
Was I meant to discover this flaw in this function?

Here is my attempt at an explanation of why it always maximises, except in this one situation:

Situation 1
When both A and B are truthy numbers, and A is greater than B
e.g. when A is 10 and B is 5

10 > 5  =>  true       &&       10 => truthy      ||      5 => truthy

true && 10   // returns the second truthy  =>  10

10 || 5   // returns the first truthy  =>  10

Result: the function returns 10 (the higher number)

Situation 2
When both A and B are truthy numbers, and A is less than B
e.g. when A is 5 and B is 10

5 > 10  =>  false       &&       5 => truthy      ||      10 => truthy

false && 5   =>   false

false || 10   =>   10

Result: the function returns 10 (the higher number)

Situation 3
When A is a truthy number, B is 0 (falsy), and A is greater than B
e.g. when A is 10 and B is 0

10 > 0  =>  true       &&       10 => truthy      ||      0 => falsy

true && 10   =>   returns the second truthy  =>  10

10 || 0   =>   10

Result: the function returns 10 (the higher number)

Situation 4
When A is a truthy number, B is 0 (falsy), and A is less than B
e.g. when A is -10 and B is 0

-10 > 0  =>  false       &&       -10 => truthy      ||      0 => falsy

false && -10   =>   false

false || 0   =>   0

Result: the function returns 0 (the higher number)

Situation 5 - THE EXCEPTION
When A is 0 (falsy), B is a truthy number, and A is greater than B
e.g. when A is 0 and B is -10

0 > -10  =>  true       &&       0 => falsy      ||      -10 => truthy

true && 0   =>   0

0 || -10   =>   -10

Result: the function returns -10 (the LOWER number)

Situation 6
When A is a 0 (falsy), B is a truthy number, and A is less than B
e.g. when A is 0 and B is 10

0 > 10  =>  false       &&       0 => falsy      ||      10 => truthy

false && 0   =>   false

false || 10   =>   10

Result: the function returns 10 (the higher number)

Situation 7
When both A and B are 0 (falsy)

0 > 0  =>  false       &&       0 => falsy      ||      0 => falsy

false && 0   =>   false

false || 0   =>   0

Result: the function returns 0 (the only number, and therefore the maximum)

Situation 8
When A and B are the same truthy number.
e.g. when A and B are both 10

10 > 10  =>  false       &&       10 => truthy      ||      10 => truthy

false && 10   =>   false

false || 10   =>   10

Result: the function returns 10 (the only number, and therefore the maximum)

Situation 9
When A and B are the same truthy number, and this number is negative.
e.g. when A and B are both -10

-10 > -10  =>  false       &&       -10 => truthy     ||     -10 => truthy

false && -10   =>   false

false || -10   =>   -10

Result: the function returns -10 (the only number, and therefore the maximum)

So, I guess we need to add an if( ) statement to the function for it to work with any pair of numbers, as follows:

const maximize = (a, b) => {
  if (!(a === 0 && a > b)) {     
    return a > b && a || b
  } else {
    return a
  }  
}

Any thoughts?

By whatever measure, if A is greater than B then A is returned, else B. One assumes we are comparing objects of same type.

Truthiness is not a factor, only relation, greater or not.

But what about when A is 0 and B is a negative number? In this case the A is greater than B, but B is returned instead:

D’uh. && a. Blinded by bias.

Did I plant this for you to discover? Not explicitly. Did I know this code was flawed? Yes. It has a domain of reliability. To your credit is the discovery that the domain is limited.

Zero is a special case, as may be others not thought of, yet. On the whole the logic works, but for the special case.

1 Like

:+1: Thanks for confirming, and yes I can see why zero is a special case.

Thanks for the challenge - it certainly got me thinking, and was a good follow-up on what we were discussing before about the differences between the && and || operators, short circuiting etc.

1 Like

Yes, but first, nicely done. My only expectation beyond this now would be not using if. Up for the challenge?

1 Like

Thanks!

Yep - I’m on the case :face_with_monocle:

1 Like

Good luck. I expect it may be quite convoluted if you do pull it off. There is nothing wrong with your pattern. Just curious what a single return statement would look like.

My immediate thought was to use a ternary operator, to turn the if... else into a single return statement:

const maximize = (a, b) => !(a === 0 && a > b) ? a > b && a || b : a

OR (to avoid the NOT operator condition)

const maximize = (a, b) => a === 0 && a > b ? a : a > b && a || b

However, I wanted to try to find a way to avoid having to use the extra condition altogether, and alter a > b && a || b itself, and to find a way to account for the real issue, which is when A is 0 (falsy) and greater than B (i.e. negative), the OR statement doesn’t return A (because it’s falsy), but instead returns B (the lower number). The key issue seems to be the fact that 0 as a number is falsy, whereas as a string it would be truthy. So, I’ve come up with the following to solve this:

const maximize = (a, b) => Number(a > b && a.toString() || b)

OR

const maximize = (a, b) => Number(a > b && String(a) || b)

I was already aware of the .toString() method, but finding out how to convert a number as a string back to a number was new territory for me. I don’t fully understand all the issues surrounding strings and numbers as either primitive types or objects, but either of the above seem to (i) deal with the exception and (ii) return a number type rather than a string type in all situations. However, I’m not sure if I’ve created any potential issues if the returned value is then further utilised…

Looking forward to your critique and comments! :smiley:

Very good! Two tiny tweaks, though it makes little or no difference.

+(a > b && String(a)) || b

Above we act on the operand individually, not the final yield. The unary plus operator is valid shorthand for coercing to Number. Is there a weakness in this that can be sussed out? Or is there no real difference?

By having the unary plus operator act on the AND operand individually, there is no improvement to the original a > b && a || b and the exception is not addressed. This is because the issue is having a 0 as a number passed to the second OR operand.

With this AND then OR setup, there is no way I can see of only acting on the second OR operand. For example, a > b && +(String(a) || b) doesn’t work either, for obvious reasons.

So, I think we need this:

+(a > b && String(a) || b)

Agreed?

Here’s a link to documention on the '+ unary operator’ in case anyone else reading this would find it helpful:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus_()

1 Like

Yes, agreed. Again you discovered the weakness and addressed it, giving good support to your argument. Well done.

Now if only it were as simple to coerce a string. What has your research shown, thus far?

+(a > b && a + "" || b)

You think?

1 Like

I’ll be in touch… saving some of the fun for another day! :wink: :face_with_monocle:

1 Like

Yes, that works.
It also works in reverse:

+(a > b && '' + a || b)

I’m tempted to write the coerce A to string bit of code without spacing, as I think it gives it more visual clarity:

+(a > b && a+'' || b)

But I’m not sure whether the lack of spacing would be considered a crime against best practice… ?

I know we want to keep brackets to a minimum, but maybe some extra brackets around our coerce A to string bit of code may add some clarity and make reading it easier:

+(a > b && (a + '') || b)

What’s your verdict?

Any preference between (a + '') and ('' + a) ?

9 of 10 users will agree it is better to leave whitespace around binary operators. Unary operators behave more like signs.

+n
-n

As to the order, I chose the variable as the key, so as to pretend it is already a string.

My preferred?

a + ''

Question

Where are +, >, and && in the pecking order by precedence rules? Brackets are only needed to override the order. That said, one’s own perception of readability may vary from the objective norm.

That’s to be expected as we develop our own norms which is a short and long term goal. Still, don’t be drawn into adding what is not needed. It’s more for the brain to get around and more bytes in the file and in memory.

 > maxmize = (a, b) => +(a > b && a + "" || b)
<- (a, b) => +(a > b && a + "" || b)
 > maxmize(0, -10)
<- 0
 > maxmize(-10, 0)
<- 0

@jon_morris, I’ll give you first billing for the work you did to suss out the errors and work with type conversions. How it all boiled down came about by your dilligence and effort.

1 Like

Great! Thanks, for those final comments - really helpful.

I can now see that it’s preferable to keep the whitespace in a + "" to avoid confusion between unary plus and binary plus (i.e. addition) operators.

Having found this comprehensive operator pecking order…
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table
… I can also now see why we don’t need brackets around a + "".

Operator Order of Precedence

  1. unary plus
  2. “binary plus” i.e addition
  3. > (greater than)
  4. &&
  5. ||

So,

  • we need the brackets around the whole statement*, otherwise the unary plus operator would only act on A

  • we don’t need to partition off a + "" with brackets, as this will be parsed before the logical operators (positioned before and after it) have any effect.

*or is the correct term here expression rather than statement ?

1 Like
console.log('some expression')        // some expression

Does the above yield a value? Answer: No. That means it is not an expression. The argument is a string literal expression, though, but that doesn’t make the whole thing one.

console.log(a = 'some expression')    // some expression
console.log(a)                        // some expression

Notice how we don’t log the statement, only the expression being assigned? I know, this is a bit of weirdness, but it is valid and allowed because of the way console.log() susses out the expression portion.

a + ""

is an expression since it yields a value.

The whole statement would include the return keyword. Take that away and we are left with an expression that yields a value (which return sends back to the caller).

If it can’t be evaluated then it is not an expression in programming terms. Statements do something, expressions are something (a value).

1 Like

So in…

const maximize = (a, b) => +(a > b && a + '' || b)
+(a > b && a + '' || b)         // this is an expression

On its own, it is something, it’s a value i.e. it can be evaluated

However, if we put a return in front of it (and convert our arrow function into a function expression)…

const maximize = function (a, b) {
  return +(a > b && a + '' || b)
}
return +(a > b && a + '' || b)  // this is a statement (return statement) 

+(a > b && a + '' || b)         // but this is still an expression

The return statement does something - it returns the value of the evaluated expression.

So we have return, if, for, while, break and delete statements, for example…

Is that it? :wink:

Pretty much, yes. We don’t have to restructure the arrow function, just know there is an implicit return.

if (condition)

condition is any expression. It is first evaluated to arrive at a single value which if truthy will go to the true branch, and if falsy will go to the else branch.

which you will often find me referring to as the return value.

1 Like

You read my mind… I was wondering about a condition within an if statement…whether it’s a statement or an expression…

I think I’ve seen the term “condition statement”… not that it’s worth losing sleep over, but if we are being pedantic it’s actually an expression - an expression that evaluates to true or false (boolean values) which are used in the if statement.

Not that I’m pedantic or anything… :joy::joy:

1 Like