简体   繁体   中英

PHP usort() not accurate?

I have a multi-dimensional array. Inside each array, their is a sub value called "elo-rating". I want to sort my array based on this sub value.

So I take my array, called $newRanks and run it through the following:

uasort($newRanks, create_function(
    '$b, $a', 'return $a["elo_rating"] - $b["elo_rating"];'
));

After running through that code, my array returns as follows:

array:52 [
  "3-1154" => array:4 [
    "league_id" => 3
    "user_id" => 1154
    "elo_matches" => 8
    "elo_rating" => 1224.47797881
  ]
  "3-205" => array:4 [
    "league_id" => 3
    "user_id" => 205
    "elo_matches" => 11
    "elo_rating" => 1207.86593741
  ]
  "3-1" => array:4 [
    "league_id" => 3
    "user_id" => 1
    "elo_matches" => 17
    "elo_rating" => 1206.60264689
  ]
  "3-285" => array:4 [
    "league_id" => 3
    "user_id" => 285
    "elo_matches" => 4
    "elo_rating" => 1187.31524255
  ]
  "3-259" => array:4 [
    "league_id" => 3
    "user_id" => 259
    "elo_matches" => 4
    "elo_rating" => 1173.02391767
  ]
  "3-12689" => array:4 [
    "league_id" => 3
    "user_id" => 12689
    "elo_matches" => 4
    "elo_rating" => 1167.46830619
  ]
  "3-1603" => array:4 [
    "league_id" => 3
    "user_id" => 1603
    "elo_matches" => 13
    "elo_rating" => 1153.3060092
  ]
  "3-16" => array:4 [
    "league_id" => 3
    "user_id" => 16
    "elo_matches" => 7
    "elo_rating" => 1146.65083202
  ]
  "3-1609" => array:4 [
    "league_id" => 3
    "user_id" => 1609
    "elo_matches" => 3
    "elo_rating" => 1122.679103
  ]
  "3-333" => array:4 [
    "league_id" => 3
    "user_id" => 333
    "elo_matches" => 5
    "elo_rating" => 1112.56694511
  ]
  "3-10030" => array:4 [
    "league_id" => 3
    "user_id" => 10030
    "elo_matches" => 4
    "elo_rating" => 1091.27782914
  ]
  "3-378" => array:4 [
    "league_id" => 3
    "user_id" => 378
    "elo_matches" => 9
    "elo_rating" => 1082.0354022
  ]
  "3-6107" => array:4 [
    "league_id" => 3
    "user_id" => 6107
    "elo_matches" => 5
    "elo_rating" => 1059.74850166
  ]
  "3-5179" => array:4 [
    "league_id" => 3
    "user_id" => 5179
    "elo_matches" => 3
    "elo_rating" => 1046.60181418
  ]
  "3-1476" => array:4 [
    "league_id" => 3
    "user_id" => 1476
    "elo_matches" => 9
    "elo_rating" => 1038.88789903
  ]
  "3-70" => array:4 [
    "league_id" => 3
    "user_id" => 70
    "elo_matches" => 8
    "elo_rating" => 1038.63959146
  ]
  "3-303" => array:4 [
    "league_id" => 3
    "user_id" => 303
    "elo_matches" => 7
    "elo_rating" => 1039.26666217
  ]
  "3-59" => array:4 [
    "league_id" => 3
    "user_id" => 59
    "elo_matches" => 1
    "elo_rating" => 1033.78309445
  ]
  "3-1017" => array:4 [
    "league_id" => 3
    "user_id" => 1017
    "elo_matches" => 4
    "elo_rating" => 1002.79264647
  ]
  "3-632" => array:4 [
    "league_id" => 3
    "user_id" => 632
    "elo_matches" => 3
    "elo_rating" => 1002.2039368
  ]
  "3-177" => array:4 [
    "league_id" => 3
    "user_id" => 177
    "elo_matches" => 4
    "elo_rating" => 994.838857477
  ]
  "3-12466" => array:4 [
    "league_id" => 3
    "user_id" => 12466
    "elo_matches" => 4
    "elo_rating" => 994.761652125
  ]
  "3-9725" => array:4 [
    "league_id" => 3
    "user_id" => 9725
    "elo_matches" => 7
    "elo_rating" => 994.520367143
  ]
  "3-1593" => array:4 [
    "league_id" => 3
    "user_id" => 1593
    "elo_matches" => 4
    "elo_rating" => 987.448354356
  ]
  "3-78" => array:4 [
    "league_id" => 3
    "user_id" => 78
    "elo_matches" => 16
    "elo_rating" => 984.927509938
  ]
  "3-20837" => array:4 [
    "league_id" => 3
    "user_id" => 20837
    "elo_matches" => 4
    "elo_rating" => 981.533602402
  ]
  "3-25" => array:4 [
    "league_id" => 3
    "user_id" => 25
    "elo_matches" => 3
    "elo_rating" => 977.651701927
  ]
  "3-2056" => array:4 [
    "league_id" => 3
    "user_id" => 2056
    "elo_matches" => 8
    "elo_rating" => 978.374247502
  ]
  "3-14300" => array:4 [
    "league_id" => 3
    "user_id" => 14300
    "elo_matches" => 9
    "elo_rating" => 958.218292232
  ]
  "3-16900" => array:4 [
    "league_id" => 3
    "user_id" => 16900
    "elo_matches" => 3
    "elo_rating" => 957.66758785
  ]
  "3-5" => array:4 [
    "league_id" => 3
    "user_id" => 5
    "elo_matches" => 3
    "elo_rating" => 955.441682773
  ]
  "3-11793" => array:4 [
    "league_id" => 3
    "user_id" => 11793
    "elo_matches" => 3
    "elo_rating" => 956.118019821
  ]
  "3-23" => array:4 [
    "league_id" => 3
    "user_id" => 23
    "elo_matches" => 1
    "elo_rating" => 950.0
  ]
  "3-160" => array:4 [
    "league_id" => 3
    "user_id" => 160
    "elo_matches" => 6
    "elo_rating" => 946.346810828
  ]
  "3-11882" => array:4 [
    "league_id" => 3
    "user_id" => 11882
    "elo_matches" => 3
    "elo_rating" => 943.113557791
  ]
  "3-178" => array:4 [
    "league_id" => 3
    "user_id" => 178
    "elo_matches" => 3
    "elo_rating" => 940.38037017
  ]
  "3-2113" => array:4 [
    "league_id" => 3
    "user_id" => 2113
    "elo_matches" => 3
    "elo_rating" => 940.343382565
  ]
  "3-1334" => array:4 [
    "league_id" => 3
    "user_id" => 1334
    "elo_matches" => 2
    "elo_rating" => 923.336202927
  ]
  "3-184" => array:4 [
    "league_id" => 3
    "user_id" => 184
    "elo_matches" => 2
    "elo_rating" => 920.326252901
  ]
  "3-2162" => array:4 [
    "league_id" => 3
    "user_id" => 2162
    "elo_matches" => 2
    "elo_rating" => 917.932985501
  ]
  "3-2058" => array:4 [
    "league_id" => 3
    "user_id" => 2058
    "elo_matches" => 6
    "elo_rating" => 905.641833006
  ]
  "3-1951" => array:4 [
    "league_id" => 3
    "user_id" => 1951
    "elo_matches" => 2
    "elo_rating" => 906.136056131
  ]
  "3-1749" => array:4 [
    "league_id" => 3
    "user_id" => 1749
    "elo_matches" => 2
    "elo_rating" => 905.570092295
  ]
  "3-15296" => array:4 [
    "league_id" => 3
    "user_id" => 15296
    "elo_matches" => 2
    "elo_rating" => 901.02829192
  ]
  "3-11684" => array:4 [
    "league_id" => 3
    "user_id" => 11684
    "elo_matches" => 2
    "elo_rating" => 901.02829192
  ]
  "3-940" => array:4 [
    "league_id" => 3
    "user_id" => 940
    "elo_matches" => 2
    "elo_rating" => 899.735074733
  ]
  "3-12235" => array:4 [
    "league_id" => 3
    "user_id" => 12235
    "elo_matches" => 2
    "elo_rating" => 900.0
  ]
  "3-2957" => array:4 [
    "league_id" => 3
    "user_id" => 2957
    "elo_matches" => 2
    "elo_rating" => 900.0
  ]
  "3-14959" => array:4 [
    "league_id" => 3
    "user_id" => 14959
    "elo_matches" => 2
    "elo_rating" => 894.798068073
  ]
  "3-779" => array:4 [
    "league_id" => 3
    "user_id" => 779
    "elo_matches" => 5
    "elo_rating" => 874.675970857
  ]
  "3-110" => array:4 [
    "league_id" => 3
    "user_id" => 110
    "elo_matches" => 4
    "elo_rating" => 849.309123925
  ]
  "3-5837" => array:4 [
    "league_id" => 3
    "user_id" => 5837
    "elo_matches" => 4
    "elo_rating" => 821.601462523
  ]
]

If you look closely at this resulting array, you'll notice that its not exactly sorted properly. For instance, it puts 1039.26666217 below both 1038.63959146 and 1038.88789903 .

Any ideas on how to fix this?

You are returning decimal values from your function, which the documentation warns against.

The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second. Note that before PHP 7.0.0 this integer had to be in the range from -2147483648 to 2147483647.

Caution Returning non-integer values from the comparison function, such as float, will result in an internal cast to integer of the callback's return value. So values such as 0.99 and 0.1 will both be cast to an integer value of 0, which will compare such values as equal.

Change your function to return an integer to fix the issue.

uasort($newRanks, create_function(
    '$b, $a', 'return ($a["elo_rating"] > $b["elo_rating"])?-1:1;'
));

Two problems:

  1. You're using create_function() which uses eval() internally, which is a gaping security hole, and unnecessary to boot. Just use an actual anonymous function.
  2. Your function arguments are backwards.
  3. Willem Renzema raises a valid point about the implicit cast of the return value, but I don't like the solution.

So:

$epsilon = 0.000000001;
uasort(
  $newRanks,
  function($a,$b)use($epsilon}{
    $diff = $a["elo_rating"] - $b["elo_rating"];
    if( abs($diff) < $epsilon ) { return 0; }
    else if( $diff > 0 ) { return 1; }
    else { return -1; }
  )
);

Where $epsilon is a value chosen specifically for this comparison where you consider any $diff smaller than that to be an equivalence, aka float/rounding error.

See: What is the most effective way for float and double comparison?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM