vote up 2 vote down star

Hi guys,

I am currently creating a sorting method that consists of values from an mysql query.

Here's a brief view of the array:

    Array
    (
        [0] => Array
            (
                ['id'] = 1;
                ['countries'] = 'EN,CH,SP';
            )
        [1] => Array
            (
                ['id'] = 2;
                ['countries'] = 'GE,SP,SV';
            )
    )

I have succeeded in making a normal usort based on the numeric id values, but I rather want to sort the array by the content of the "countries" field (if it contains a set string, a country code in this case), and then by the id field.

The following snippet was my first idea of how to do it, but I have no idea of how to incorporate it into an working function:

in_array('EN', explode(",",$a['countries']) );

How would you do it?

Thanks!

flag

Out of curiosity, why don't you get them sorted out of the DB in the first place, e.g. ORDER BY country, id – Gordon Jan 13 at 18:45
Its already sorted by ID in the first place, as you say, but that doesn't solve the in_array-part of the issue though... – Industrial Jan 13 at 18:49

6 Answers

vote up 5 vote down check

Personally, I would use a custom (anonymous) function in conjunction with usort().

EDIT: Re - your comment. Hopefully this will put you on the right track. This function gives equal priority to elements which both have EN or neither have EN, or adjusted priority when just one has EN.

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($a['countries'],'EN');
    if (($ac !== false && $bc !== false) || ($ac == false && $bc == false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    else {
        return -1;
    }
});

This function, on the other hand, gives equal priority if both have EN, higher if one has EN, and does a text comparison if neither has EN.

usort($array,function ($a, $b) {
    $ac = strpos($a['countries'],'EN');
    $bc = strpos($a['countries'],'EN');
    if ($ac !== false && $bc !== false)) {
        return 0;
    }
    elseif ($ac !== false) {
        return 1;
    }
    elseif ($bc !== false) {
        return -1;
    }
    else {
        if ($a['countries'] == $b['countries']) {
            return 0;
        }
        elseif($a['countries'] > $b['countries']) {
            return 1;
        }
        else {
            return -1;
        }
    }
});

Again, hopefully this will give you enough direction to move forward on your own. If you are having any problems, feel free to post more comments and I'll try to help. A note if you're tying to compare multiple properties with weight: try out a funky switch block, e.g.

$ac = array_flip(explode(',',$a['countries']));
$bc = array_flip(explode(',',$b['countries']));
switch (true) {
    case array_key_exists('EN',$ac) && !array_key_exists('EN',$bc):
        return 1;
    case array_key_exists('DE',$ac) && !array_key_exists('EN',$bc) && !array_key_exists('EN',$bc):
        return 1;
    // and so on
}

More Edits!

Actually, I was thinking more on the problem of complex sorting, and I have come up with the following solution, for your consideration. It will allow you to define numerical rankings based on keywords which would appear in the countries index. Here is the code, including an example:

Example Array

$array = array(
    array(
        'countries' => 'EN,DE,SP',
    ),
    array(
        'countries' => 'EN,CH,SP',
    ),
    array(
        'countries' => 'DE,SP,CH',
    ),
    array(
        'countries' => 'DE,SV,SP',
    ),
    array(
        'countries' => 'EN,SP,FR',
    ),
    array(
        'countries' => 'DE,FR,CH',
    ),
    array(
        'countries' => 'CH,EN,SP',
    ),

);

Sorting Routine

$rankings = array(
    'EN' => 10,
    'SP' => 8,
    'FR' => 7,
    'DE' => 5,
    'CH' => 3,
    'SV' => 1,
);
usort($array, function (&$a, &$b) use ($rankings) {
    if (isset($a['_score'])) {
        $aScore = $a['_score'];
    }
    else {
        $aScore = 0;
        $aCountries = explode(',',$a['countries']);
        foreach ($aCountries as $country) {
            if (isset($rankings[$country])) {
                $aScore += $rankings[$country];
            }
        }
        $a['_score'] = $aScore;
    }

    if (isset($b['_score'])) {
        $bScore = $b['_score'];
    }
    else {
        $bScore = 0;
        $bCountries = explode(',',$b['countries']);
        foreach ($bCountries as $country) {
            if (isset($rankings[$country])) {
                $bScore += $rankings[$country];
            }
        }
        $b['_score'] = $bScore;
    }
    if ($aScore == $bScore) {
        return 0;
    }
    elseif ($aScore > $bScore) {
        return -1;
    }
    else {
        return 1;
    }
});

Note: This code will sort the highest ranking entires to the top of the array. If you want reverse behavior, change this:

    elseif ($aScore > $bScore) {

to

    elseif ($aScore < $bScore) {

Note that the greater-than was changed to a less-than symbol. Making this change will result in the lowest ranking entries being sorted to the top of the array. Hope all this helps!

NOTE ALSO!

This code will make a small change to your array, in that it adds the _score element to each array. Hopefully this is not a problem, as by storing this value I was literally able to increase speed by more than double (.00038-.00041 down to .00016-.00018 in my benchmarks). If not, remove the if blocks that retrieve the cached value and let the contents of the else blocks execute every time, except of course for the part which stores the score value.

By the way, here's a var_export() dump of the array after it was sorted:

array (
  0 => array (
    'countries' => 'EN,SP,FR',
    '_score' => 25,
  ),
  1 => array (
    'countries' => 'EN,DE,SP',
    '_score' => 23,
  ),
  2 => array (
    'countries' => 'EN,CH,SP',
    '_score' => 21,
  ),
  3 => array (
    'countries' => 'CH,EN,SP',
    '_score' => 21,
  ),
  4 => array (
    'countries' => 'DE,SP,CH',
    '_score' => 16,
  ),
  5 => array (
    'countries' => 'DE,FR,CH',
    '_score' => 15,
  ),
  6 => array (
    'countries' => 'DE,SV,SP',
    '_score' => 14,
  ),
)

Enjoy!

link|flag
Hi! Awesome stuff. Thanks for your help! I am not sure that i have explained myself properly though. I want to check if the values inside the "countries"-array contains a set value (like "EN") and if so, raise the priority in sorting on the parent array. – Industrial Jan 13 at 19:03
Big thanks to you Dereleased! – Industrial Jan 14 at 17:38
1  
I just added a huge amount of info on a numerical user-defined sorting mechanism that you might like, please check it out and let me know what you think! – Dereleased Jan 14 at 23:39
Jeez man, you're amazing! I dont know what to say. Thanks! Definitely need to try this solution out! I owe you one for this! Thanks again! – Industrial Jan 17 at 11:18
Had help of this again. You're the man, Dereleased! :) – Industrial Mar 10 at 22:33
vote up 0 vote down

You might consider array_walk and array_walk_recursive and array_map, which when combined together maybe to get to doing what you want to do.

link|flag
vote up 0 vote down

Try with array_mulisort.

link|flag
vote up 0 vote down

Check out uasort to see how to use a user defined comparison function.

link|flag
Nvm, Dereleased was faster :) – Thilo Jan 13 at 18:53
vote up 0 vote down

Hi again! I am really getting nowhere with this unfortunately.

Here is what I have for the moment, and its giving me nothing but errors: uasort() [function.uasort]: Invalid comparison function

function compare($a, $b) {
    global $usercountry;

        if ( in_array($usercountry, $a['countries']) && in_array($usercountry, $a['countries']) ) {
            $return = 0;
        }

        else if (in_array($usercountry, $a['countries'])) {
            $return = 1;
        }

        else {
            $return = -1;
        }

        return $return;


        }

        $array= usort($array, "compare");

Is there anyone who might give me a hint of how to go on with it?

link|flag
vote up 0 vote down

Finally found this wonderful function at PHP.net:

        function array_msort($array, $cols)
        {
            $colarr = array();
            foreach ($cols as $col => $order) {
                $colarr[$col] = array();
                foreach ($array as $k => $row) { $colarr[$col]['_'.$k] = strtolower($row[$col]); }
            }
            $eval = 'array_multisort(';
            foreach ($cols as $col => $order) {
                $eval .= '$colarr[\''.$col.'\'],'.$order.',';
            }
            $eval = substr($eval,0,-1).');';
            eval($eval);
            $ret = array();
            foreach ($colarr as $col => $arr) {
                foreach ($arr as $k => $v) {
                    $k = substr($k,1);
                    if (!isset($ret[$k])) $ret[$k] = $array[$k];
                    $ret[$k][$col] = $array[$k][$col];
                }
            }
            return $ret;

        }

This is how each country looks like: $array['countries'] = in_array($needle, $haystack); }

$array = $array = array_msort($array, array('countries'=>SORT_DESC, 'id'=>SORT_ASC));

Thanks all for your help!

link|flag

Your Answer

Get an OpenID
or
never shown

Not the answer you're looking for? Browse other questions tagged or ask your own question.