73

So we got this function in PHP

strcmp(string $1,string $2) // returns -1,0, or 1;

We Do not however, have an intcmp(); So i created one:

function intcmp($a,$b) {
    if((int)$a == (int)$b)return 0;
    if((int)$a  > (int)$b)return 1;
    if((int)$a  < (int)$b)return -1;
}

This just feels dirty. What do you all think?

this is part of a class to sort Javascripts by an ordering value passed in.

class JS
{
    // array('order'=>0,'path'=>'/js/somefile.js','attr'=>array());
    public $javascripts = array(); 
    ...
    public function __toString()
    {
        uasort($this->javascripts,array($this,'sortScripts'));
        return $this->render();
    }
    private function sortScripts($a,$b)
    {
        if((int)$a['order'] == (int)$b['order']) return 0;
        if((int)$a['order'] > (int)$b['order']) return 1;
        if((int)$a['order'] < (int)$b['order']) return -1;
    }
    ....
}
MPelletier
  • 16,256
  • 15
  • 86
  • 137
Chase Wilson
  • 1,477
  • 1
  • 12
  • 19
  • 1
    Wouldn't `version_compare()` do the trick? – Alix Axel May 18 '10 at 00:34
  • For the curious: I have a use-case that needs it to be 1/0/-1. I have a function that sorts arrays, and can have 3 criteria. I weight the first criteria more than the second, so for a string I can use `strcasecmp($a1, $b1) * $factor`but for integers I'll have to make a function with the verbose-ish if statements. – Redzarf Oct 29 '15 at 20:49
  • This is relevant for software version string comparison, e.g. version 1.3.10 is newer than 1.3.9. This is best performed by integer comparison of parts of the version strings. – Hummeling Engineering BV Jul 18 '18 at 12:28
  • 1
    Added in php 7 as [<=> "spaceship" operator](https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.spaceship-op). E.g. `return $a <=> $b`. Or to ensure compared as ints: `return (int)$a <=> (int)$b`. – ToolmakerSteve Jun 09 '20 at 23:35
  • The spaceship operator will treat two numeric strings as numbers when comparing them. This is built into the beautiful, beautiful magic of the spaceship operator. There is no need to explicitly cast each value as an `int` or `float`. – mickmackusa May 13 '22 at 04:21

7 Answers7

122

Sort your data with:

function sortScripts($a, $b)
{
    return $a['order'] - $b['order'];
}

Use $b-$a if you want the reversed order.

If the numbers in question exceed PHP's integer range, return ($a < $b) ? -1 : (($a > $b) ? 1 : 0) is more robust.

Chuck Le Butt
  • 47,570
  • 62
  • 203
  • 289
Nicolas Viennot
  • 3,921
  • 1
  • 21
  • 22
  • 1
    I had to use ($b-$a)*-1 for reverse order. but I digress. Thanks! – Chase Wilson May 17 '10 at 21:35
  • 5
    This implementation is [broken](http://ideone.com/N5v06H) just as in many other languages. – aioobe Nov 24 '13 at 20:18
  • 13
    To clarify what @aioobe said, this implementation breaks if the integer exceeds php's integer range (64-bit signed) and is implicitly converted to a floating-point value. Therefore, while `($a - $b)` is obviously much faster, it is not as robust as `($a < $b) ? -1 : (($a > $b) ? 1 : 0)` which works in all cases. – etherice Dec 16 '13 at 22:28
  • min(max($a - $b, -1), 1) works well for integers, but if you need to deal with floats as well and you want to have the range >-1... < 0 > .. < 1 map to -1 or 1 the above nested ternary is probably still better. – Jon Marnock Mar 08 '19 at 04:04
  • This breaks also on (small) decimal numbers. So, better use a more robust version. – Jānis Elmeris Jan 02 '21 at 14:13
  • The subtraction and the ternary workaround are no longer necessary [with the invention of the spaceship operator](https://3v4l.org/Qogas). – mickmackusa May 13 '22 at 04:38
14

Purely as some additional information, there has been an accepted RFC for this (https://wiki.php.net/rfc/combined-comparison-operator).

So, the comparison function would be along the lines of ...

<?php
$data = [...];
usort($data, function($left, $right){ return $left <=> $right; });
?>

A few really nice feature here is that the comparison is done in exactly the same way as all other comparisons. So type juggling will happen as expected.

As yet, there is no magic __forCompare() like method to allow an object to expose a comparison value. The current proposal (a different RFC) is to have each object be injected into every other object during the comparison so that it does the comparison - something which just seems odd to me - potential opportunity for recursion and stack overflow ... ! I would have thought either injecting the type of object for comparison (allowing an object the ability to represent appropriate values depending upon the type of comparison) or a blind request for a value that the object can serve up for comparison, would have been a safer solution.

Not yet integrated into PHP-NG (PHP 7 at the moment), but hopefully will be soon.

Richard A Quadling
  • 3,769
  • 30
  • 40
13

why reinventing the wheel? http://php.net/manual/en/function.strnatcmp.php

echo strnatcmp(1, 2) . PHP_EOL; // -1
echo strnatcmp(10, 2) . PHP_EOL; // 1
echo strnatcmp(10.5, 2) . PHP_EOL; // 1 - work with float numbers
echo strnatcmp(1, -2) . PHP_EOL; // 1 - work with negative numbers

Test it here: https://3v4l.org/pSANR

  • 1
    This also works for all cases when either just the first or second parameter is a string. – Katrina Nov 27 '17 at 22:15
  • Sorry, but is this a joke? You have two numbers, and you compare them by converting them to strings, then the function converts them back to integers and compares them, just as you would have done it yourself in the first place. – Gábor Sep 11 '21 at 13:08
  • Very helpful to order strings such as "user.1", "user.2", "user.10", ... in the right (natural) order. I used it with PHP's usort. – Avatar Aug 22 '23 at 05:02
10

You could use

function intcmp($a,$b)
    {
    return ($a-$b) ? ($a-$b)/abs($a-$b) : 0;
    }

Although I don't see the point in using this function at all

nico
  • 50,859
  • 17
  • 87
  • 112
  • This worked like a charm! Do you think it works within the context of the class? Or would there be a better implementation? I haven't found a good standard for sorting multi-dimensional arrays yet!! – Chase Wilson May 17 '10 at 20:56
  • 4
    It's clever oneliner and all, but 1 line is not always more simple than 3. I would be scratching my head if I came across this code just to realize 5 minutes later than all it does is returns +1/0/-1 with all those divisions and modulus. I would just go with original version tbh. – serg May 17 '10 at 21:33
  • 1
    You'd be scratching your head if you came across a subtraction and a division? Seriously? – nico Jul 19 '13 at 09:37
  • Cringeworthy. An abs, a divide, and two subtracts, to implement a trivial, commonplace function? Please, just do the obvious, like everyone else. Your future code readers will thank you. – ToolmakerSteve Jun 09 '20 at 23:20
  • @ChaseWilson sorting multidimensional arrays is [super simple/elegant with `usort()` and the spaceship operator](https://stackoverflow.com/a/54647220/2943403). – mickmackusa May 13 '22 at 04:27
5

Does it have to be +1 and -1? If not, just return (int) $a - (int) $b. I don't like the divide that someone else recommended, and there's no need to check for all three cases. If it's not greater and not equal, it must be less than.

return (int) $a > (int) $b ? 1 : (int) $a == (int) $b ? 0 : -1;
tomlogic
  • 11,489
  • 3
  • 33
  • 59
  • 1
    IMO stacking ternary operators is poor practice. In this case, we have an issue with order of operations. Correction: return (int) $a > (int) $b ? 1 : ( (int) $a == (int) $b ? 0 : -1 ); – msun Jun 23 '15 at 17:31
  • Does PHP have different order of operators than C? How else would the interpreter parse this statement without the added parentheses? – tomlogic Jun 25 '15 at 16:33
  • 1
    Yes, the ternary operator associativity in PHP is broken: http://phpsadness.com/sad/30 – MazeChaZer Apr 12 '17 at 13:35
1

At a glance, yes it feels dirty. Except there must be a good reason you wrote that instead of just using the actual ==, >, and < operators. What was the motivation for creating this function?

If it were me, I'd probably just do something like:

$x = $a==$b ? 0 : ($a>$b ? 1 : ($a<$b ? -1 : null));

I realize this is just as ugly, and the : null; - not sure if PHP requires it or if I could have just done :; but I don't like it and that code should never execute anyway... I think I'd be a lot less confused about this if I knew the original requirements!

FrustratedWithFormsDesigner
  • 26,726
  • 31
  • 139
  • 202
1

For strings

 usort($points, function ($a, $b) use ($orderFlag, $key1, $key2) {
        return strcmp($a[$key1][$key2], $b[$key1][$key2]) * $orderFlag;
    });

orderFlag => 1 (ascending): -1 (descending)

For numbers

usort($points, function ($a, $b) use ($orderFlag, $key1, $key2) {
    return ($a[$key1][$key2] - $b[$key1][$key2]) * $orderFlag;
});

orderFlag => 1 (ascending): -1 (descending)

  • I would never use either of these snippets. Both can be replace by using the spaceship operator -- no reason to have separate techniques at all. – mickmackusa May 13 '22 at 04:31