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.

user2864740
  • 60,010
  • 15
  • 145
  • 220
Nikita240
  • 1,377
  • 2
  • 14
  • 29
  • 1
    You're doing a [strcmp()](http://www.php.net/manual/en/function.strcmp.php) (string comparison) in your usort, but the data you're doing it on is a float – Mark Baker Aug 15 '14 at 11:18
  • @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. – Nikita240 Aug 15 '14 at 11:18
  • @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. – Nikita240 Aug 15 '14 at 11:19
  • @Nikzilla Output shown perfectly ordered in alphanumerical order, what seems wrong? – Cthulhu Aug 15 '14 at 11:20
  • 4
    @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 me – Mark Baker Aug 15 '14 at 11:21

4 Answers4

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

Marcin Nabiałek
  • 109,655
  • 42
  • 258
  • 291
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;
user2864740
  • 60,010
  • 15
  • 145
  • 220
  • 1
    oooooooooooooooooooh, that is interesting, and makes a lot of sense. Thank you for pointing that out. – Nikita240 Aug 15 '14 at 11:23
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.

Ende Neu
  • 15,581
  • 5
  • 57
  • 68
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/