2

I have an array containing strings of this format:

[0] => "title|url|score|user|date"
[1] => "title|url|score|user|date"
[2] => "title|url|score|user|date"
[3] => "title|url|score|user|date"
...

The score field is an int that is not always unique (for example, more than one entry can have a score of 0). I'm looking to sort the strings in this array based on their score value. Originally, I tried iterating through the array and making a new one with keys corresponding to the vote score. I soon realized that you can't have duplicate keys in an array.

Is there a good clean way of doing this?

6 Answers6

2

Look into PHP's usort function

function score_sort($rec1, $rec2)
{
  return $rec1['score'] - $rec2['score'];
}

usort($score_array);

Replace ['score'] with however you are extracting the scores from the strings

hair raisin
  • 2,618
  • 15
  • 13
2
$array = array(
    0 => "title|url|12|user|date",
    1 => "title|url|0|user|date",
    2 => "title|url|13|user|date",
    3 => "title|url|0|user|date"
);

function sortOnScore( $a, $b )
{
    // discard first two values
    list( ,,$scoreA ) = explode( '|', $a );
    list( ,,$scoreB ) = explode( '|', $b );

    return $scoreA == $scoreB ? 0 : ( $scoreA > $scoreB ? 1 : -1 );
}

usort( $array, 'sortOnScore' );

var_dump( $array );
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
1

First you need to turn the strings into arrays with explode so you can do the comparisons:

// If using PHP >= 5.3, this can also be made into an anonymous function
function converter($string) {
    $result = array_combine(
        array('title', 'url', 'score', 'user', 'date'),
        explode('|', $string)
    );

    // When these are later compared, it should be as numbers
    $result['score'] = (int)$result['score'];
    return $result;
}

$input = array(
    'Foo|http://foo|0|user1|today',
    // etc.
);
$converted = array_map('converter', $input);

This will make $converted look like:

array (
  0 => array (
    'title' => 'Foo',
    'url' => 'http://foo',
    'score' => '0',
    'user' => 'user1',
    'date' => 'today',
  ),
)

Then you can sort the array using the code from my answer here by easily specifying any sort criteria you want:

usort($converted, make_converter('score', 'date', 'title'));
Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
1

Personally I'd be tempted to iterate through the array, split it by the |'s and put it into a new multi-dimensional array, for example something like this:

[0] => array([title]=>'title',[url]=>'url',[score]=>'score',[user]=>'user',[date]=>'date')
[1] => array([title]=>'title',[url]=>'url',[score]=>'score',[user]=>'user',[date]=>'date')

Then it becomes easy to sort, just use a function like this:

function sortmulti ($array, $index, $order, $natsort=FALSE, $case_sensitive=FALSE) {
         if(is_array($array) && count($array)>0) {
             foreach(array_keys($array) as $key) { 
                $temp[$key]=$array[$key][$index];
             }
             if(!$natsort) {
                 if ($order=='asc') {
                     asort($temp);
                 } else {    
                     arsort($temp);
                 }
             }
             else 
             {
                 if ($case_sensitive===true) {
                     natsort($temp);
                 } else {
                     natcasesort($temp);
                 }
                if($order!='asc') { 
                 $temp=array_reverse($temp,TRUE);
                }
             }
             foreach(array_keys($temp) as $key) { 
                 if (is_numeric($key)) {
                     $sorted[]=$array[$key];
                 } else {    
                     $sorted[$key]=$array[$key];
                 }
             }
             return $sorted;
         }
     return $sorted;
 }

i.e. do this:

$sortedarray = sortmulti($array,'score','asc');
Nick
  • 6,316
  • 2
  • 29
  • 47
1

It would be very easy using the asort function:

$pattern = '#^([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)$#';
$sorted = array();
foreach($data as $s) $sorted[] = preg_replace($pattern, '$3|$1|$2|$4|$5', $s);
asort($sorted);


these 4 lines of code, when given $data:

Array
(
    [0] => title_0|url_0|6|user_0|date_0
    [1] => title_1|url_1|6|user_1|date_1
    [2] => title_2|url_2|2|user_2|date_2
    [3] => title_3|url_3|3|user_3|date_3
    [4] => title_4|url_4|2|user_4|date_4
    [5] => title_5|url_5|7|user_5|date_5
    [6] => title_6|url_6|3|user_6|date_6
    [7] => title_7|url_7|8|user_7|date_7
    [8] => title_8|url_8|3|user_8|date_8
    [9] => title_9|url_9|9|user_9|date_9
)

will generate $sorted:

Array
(
    [2] => 2|title_2|url_2|user_2|date_2
    [4] => 2|title_4|url_4|user_4|date_4
    [3] => 3|title_3|url_3|user_3|date_3
    [6] => 3|title_6|url_6|user_6|date_6
    [8] => 3|title_8|url_8|user_8|date_8
    [0] => 6|title_0|url_0|user_0|date_0
    [1] => 6|title_1|url_1|user_1|date_1
    [5] => 7|title_5|url_5|user_5|date_5
    [7] => 8|title_7|url_7|user_7|date_7
    [9] => 9|title_9|url_9|user_9|date_9
)

and with just 2 more lines you can have the items in each element of the array back in the original order/format:

$data = array();
foreach($sorted as $s) $data[] = preg_replace($pattern, '$2|$3|$1|$4|$5', $s);

setting $data to:

Array
(
    [0] => title_2|url_2|2|user_2|date_2
    [1] => title_4|url_4|2|user_4|date_4
    [2] => title_3|url_3|3|user_3|date_3
    [3] => title_6|url_6|3|user_6|date_6
    [4] => title_8|url_8|3|user_8|date_8
    [5] => title_0|url_0|6|user_0|date_0
    [6] => title_1|url_1|6|user_1|date_1
    [7] => title_5|url_5|7|user_5|date_5
    [8] => title_7|url_7|8|user_7|date_7
    [9] => title_9|url_9|9|user_9|date_9
)
ghbarratt
  • 11,496
  • 4
  • 41
  • 41
0

Create a new array of arrays:

[0] => array("score", old_array[0])

Then sort.

Dennis
  • 14,264
  • 2
  • 48
  • 57
  • 1
    The second option would definitely fail due to the fact that `score` should be compared numerically while e.g. `title` as a string. You can't compare all of them in one go. – Jon Oct 26 '11 at 16:23
  • @Jon: Thanks for the *What was I thinking?* moment. Edited and removed. – Dennis Oct 26 '11 at 16:27