I was looking for a way to sort an array of objects via multiple properties, which are calculated on the fly. As usual, quick googling navigated me to the Stack Overflow, where I found the same question and, most importantly, answers for it.
Suggested solution from SO
If we take a look at the suggested solutions from SO, it is a combination of usort() and strcmp() functions. At first, they seem like a good combo, since usort
accepts callback function which needs to return an integer number. strcmp
returns exactly that, based on comparison it does, where:
- negative integers imply that the value of the first argument is lower than the second's
- 0 implies equal values
- positive integers imply that the value of the first argument is bigger than the second's
Here are some examples:
echo strcmp('3', '7'); // -4
echo strcmp('9', '9'); // 0
echo strcmp('5', '2'); // 3
Problem with suggested solution
Even though everything seems good at first when comparing one-digit numbers, it doesn't go well when multi-digit numbers come into play:
echo strcmp('7', '111'); // 6 (expected -104)
echo strcmp('18', '9'); // -8 (expected 9)
What happened here? Numbers are not being compared as a whole.
In the first example, number 7 is compared with the first digit of the value 111, and their difference is 6.
In the second example, first digit of the number 18 is compared with number 9, and their difference is -8.
To be precise, a binary comparison is being done in the background. strcmp
is one of many functions which are taken from the C language.
Implemented solution
To solve this problem, we don't need strcmp
or any other helper function. If we are sure that compared arguments will be integers, we can simply use good ol' subtraction to get the needed result (negative, 0 or positive):
usort($arrayOfElements, function ($firstElement, $secondElement) {
return $firstElement->getSize() - $secondElement->getSize();
});
Additionally, if you need to sort values in a descending order, just swap the subtrahend and minuend:
return $secondElement->getSize() - $firstElement->getSize();
Conclusion
Copy-pasting solutions from Stack Overflow without enough research can result in big damage.
In my case, the problem was not visible for a few days because of the values being used for testing.
Make sure to check the documentation and spend more time testing solutions.
If you have encountered similar problems by doing c/p from SO, let us know in the comments :)
Top comments (4)
strcmp
works as intended. The only thing to beware here is usingstrcmp
or equivalents to compare numeric values. This is the case in most, if not all languages, not only PHP.This is one of those things. Always dump and test. Never assume.
The clue that could have saved you sooner is this, IMHO:
You need to compare two values. Whether those are primitive values or not, the comparison always comes down to comparing two primitive type values of the same type, right? It's easy to compare primitive types. Should you cast two numeric values to string when comparing? No, right? Ok, what does
strcmp
do? It compares two string values. Do you have string values, or something else? Isstrcmp
the right choice? No, right? :)Hi Leo, sorry for the late response and thanks for your comment.
Everything works as intended, if you know what is intended :)
This is where research & testing comes in, and if you take a look at my post again (where I honestly admitted my mistake), you might see that that's exactly the point of the post.
Cheers!
Hello,
Why don't you use
strnatcmp
: php.net/manual/en/function.strnatc... ?or directly
<=>
operator?Sorry for the late response, thanks for your comment!
You are right, spaceship is a great choice for this case, and it's mentioned in the referenced SO from the intro of the post.
But, take a look at the conclusion from the post - "Copy-pasting solutions from Stack Overflow without enough research can result in big damage." Hope this answers your question :)