Why isn't my usort() output sorted?
Asked Answered
P

4

2

I'm rewriting an old script that spits out the most popular content using usort.

For some reason, the output of my usort isn't actually sorted.

I'm using php 5.5 (please disregard the use of the depreciated mysql_ function, that is part of the reason I am rewriting this script).

    //store data in array
    $sort_array = array();
    while($row = mysql_fetch_assoc($result)) {
        //calculate age
        $age = (strtotime("now") - strtotime($row["DATE"]))/86400;//86400 converts seconds to days
        //calculate "effective views" via gaussian distribution shown below 
        //y = e^(-(k * x)^2) where k is below
        $K = 0.1665109222315395512706329289790402095261177704528881;//solved for a half-life of 5 days
        $effective_views = round($row["VIEWS"] * exp(-pow( $K * $age, 2)));

        //store data
        $article = new stdClass;
        //$article->id = $row["ID"];
        $article->effective_views = $effective_views;
        //$article->title = $row["TITLE"];
        //$article->author = $row["AUTHOR"];
        $sort_array[] = $article;
    }

    //sort array based on effective views
    usort(
        $sort_array, 
        function($a, $b) {
            return -strcmp($a->effective_views, $b->effective_views);
        }
    );
    echo "<pre>";
    print_r($sort_array);

The output should be sorted on effective_views in descending order, however, it is not.

Here is an output dump:

http://pastebin.com/rV5YwWN7

Please inform me what I am doing wrong here.

Patton answered 15/8, 2014 at 11:14 Comment(5)
You're doing a strcmp() (string comparison) in your usort, but the data you're doing it on is a floatDeadly
@GordonM Because the parameters I am sorting by would be too heavy on the DB. Also part of the reason I am rewriting this script, as I mentioned is because of the deprecated mysql_ functions.Patton
@MarkBaker I round it to an integer though. Also I have checked the output of strcmp already, and it seems to be functioning as intended.Patton
@Nikzilla Output shown perfectly ordered in alphanumerical order, what seems wrong?Summon
@Nikzilla - string comparison of numbers gives 1, 10, 11, 2, 21, 3, etc.... numeric comparison of numbers gives 1, 2, 3, 10, 11, 21.... that's pretty different looking to meDeadly
P
3

The problem is using strcmp to compare numbers; it compares strings, in a lexicographical manner, or "as found in a dictionary". This makes "96" come after "503", or before when ordered in reverse.

Consider the following which returns a negative number when a < b, a positive number when a > b, and 0 otherwise - it effectively reverse orders numbers when used with a usort-style compare function.

return $a->effective_views - $b->effective_views;
Perrin answered 15/8, 2014 at 11:18 Comment(1)
oooooooooooooooooooh, that is interesting, and makes a lot of sense. Thank you for pointing that out.Patton
I
3

You should not use strcmp to compare integers.

You should use:

return $a->effective_views - $b->effective_views;

instead of

return -strcmp($a->effective_views, $b->effective_views);

You can look also for result of:

echo strcmp(2,10);

As you see it's 1 and not -1 because first character in 10 string is 1 and 1 is before 2

Inglenook answered 15/8, 2014 at 11:17 Comment(0)
P
3

The problem is using strcmp to compare numbers; it compares strings, in a lexicographical manner, or "as found in a dictionary". This makes "96" come after "503", or before when ordered in reverse.

Consider the following which returns a negative number when a < b, a positive number when a > b, and 0 otherwise - it effectively reverse orders numbers when used with a usort-style compare function.

return $a->effective_views - $b->effective_views;
Perrin answered 15/8, 2014 at 11:18 Comment(1)
oooooooooooooooooooh, that is interesting, and makes a lot of sense. Thank you for pointing that out.Patton
Q
2

You are using strcmp on what appear to be integers, try return $a->effective_views - $b->effective_views or the other way around depending on your ordering.

This simple sandbox shows that strcmp works in lexicographical fashion.

Quizzical answered 15/8, 2014 at 11:19 Comment(0)
S
0

Strcmp - Binary safe string comparison

You can not use strcmp like that because it is not to compare numbers, if you want to compare numbers using strcmp you can use a little trick like this:

function cmp($a, $b)
{
    if ($a == $b) {
        return 0;
    }
    return ($a > $b) ? -1 : 1;
}

$a = array(3, 7, 733, 9, 73, 222, 5, 99, 1, 5, 0);
usort($a, "cmp");

foreach ($a as $key => $value) {
    echo "$key: $value\n";
}

And if you want to use Comparison Operators:

function cmp2($a, $b) {
    if(strlen($a)!==strlen($b)){
    return -strcmp(strlen($a),strlen($b));
    }
    return -strcmp($a, $b);
}

$a = array(3, 7, 733, 9, 73, 222, 5, 99, 1, 5, 0);
usort($a, "cmp2");

foreach ($a as $key => $value) {
    echo "$key: $value\n";
}

Example: http://sandbox.onlinephpfunctions.com/

Steverson answered 6/6, 2017 at 0:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.