0

here is my array sample $data[][];

Array( [0] => Array ( [id] => 1349
                      [rating1] => 1.9378838029981E-7
                      [rating2] => 1.1801796607774     )
       [1] => Array ( [id] => 1350
                      [rating1] => 5.5499981876923E-7
                      [rating2] => 1.5121329727308     )
       [2] => Array ( [id] => 1377
                      [rating1] => 0.00023952225410117
                      [rating2] => 2.1947077830236     )
       [3] => Array ( [id] => 1378
                      [rating1] => 0.00022982302863634
                      [rating2] => 2.2135588326622     )
       [4] => Array ( [id] => 1379
                      [rating1] => 0.00026272979843585
                      [rating2] => 2.2388295595073     )
       [5] => Array ( [id] => 1380
                      [rating1] => 0.0002788640872546
                      [rating2] => 2.1815325502993     )
)

I want to find max($data[][rating?]) but return $data[id][max_rating?] i.e. id associated with the max value.
Finding the max was easy for one particular, say rating1, I used array_reduce as follows (this is inspired from this SO ):

$max = array_reduce($data, function($a, $b) {
    return $a > $b['rating1'] ? $a : $b['rating1']; 
});

Now I have two questions :
1. How can I extend above array_reduce to include rating2 ? I have other ratingX as well.
2. I do not want the max value, rather the $data[][id] associated with the max.

I am not so much concerned about Q1, but the second one is important as I don't want to search through the array again to get associated $data[][id].
One line of thought is to use array_map instead of array_reduce, but I couldn't come up with a version which will pass on both [id] and [rating?]. Also, there are complications when I try to max() multiple rating? at one shot, as each rating will have different max, which in turn associates with different [id].
EDIT : Just to be clear, I want all the respective ids associated with respective max of each rating?

Community
  • 1
  • 1
wadkar
  • 960
  • 2
  • 15
  • 29
  • this looks like a table from a database. if so, do your filtering at the db-level! – knittl Aug 05 '11 at 13:10
  • no this is NOT, based on the data from table, I calculate rating1,rating2... and add that rating to the array already available from mysql_fetch_array() . – wadkar Aug 05 '11 at 13:22
  • you could do the rating calculation in sql too, you know. anyway, have a look at my answer and see if it works for you – knittl Aug 05 '11 at 13:23
  • hmm, lets just say its not possible and leave it at that. I am going through your suggestions. Thanks in advance :) – wadkar Aug 05 '11 at 13:31
  • if [id] is unique is there any reason why you are not using the id as the array key instead of having it as a field in the sub array? so `array(1349 => array ('rating1'=>....,'rating2'=>....),1350=> array.....` – Bob Vale Aug 05 '11 at 13:37
  • the [id] is indeed unique, corresponding to one row of table (which is not entirely true, but lets just pretend). However I need the count of how many rows I have ... which on second thought can be calculated .. thanks @bob-vale , I will think about it. – wadkar Aug 05 '11 at 13:42

2 Answers2

1

assuming your array is unsorted you have to loop through it at least once (either manually or using builtin functions). i'd use the following code:

$array = array(
  array( 'id' => 1349, 'sudhi_rating1' => 1.9378838029981E-7, 'sudhi_rating2' => 1.1801796607774 ),
  array( /* … */ ),
  /* … */
);

$idx_max = 0;
foreach($array as $idx => $item) {
  if($item['sudhi_rating1'] > $array[$idx_max]['sudhi_rating1'])
    $idx_max = $idx;
}

echo "Rating 1 has max value at id ", htmlspecialchars($array[$idx_max]['id']);

you can extend the code to check multiple ratings at once (make $idx_max an array itself and add more ifs):

$idx_max = array (
  'sudhi_rating1' => 0,
  'sudhi_rating2' => 0,
  /* … */ );
foreach($array as $idx => $item) {
  foreach($idx_max as $rating => &$max) {
    if($item[$rating] > $array[$max][$rating])
      $max = $idx;
  }
}

foreach($idx_max as $rating => $max)
  echo 'Max value for ', htmlspecialchars($rating), ' has id ', htmlspeciachars($array[$max]['id']);
knittl
  • 246,190
  • 53
  • 318
  • 364
  • the foreach(&$idx_max as $rating => $max) is generating error, if you want to pass $idx_max as reference i think $max should be replaced with &$max , i tried and it works now. Fabulous !! excellent peace of code. Thanks a lot, just one tiny problem, it returns 5,4,4 for rating1,2,3 i.e. not the 'id' but I can easily do $data[5]['id'] to get the value of 'id'. – wadkar Aug 05 '11 at 13:49
  • you fixed it, thanks for this excellent answer ! Just to improve the efficiency, what if the $data array is passed by reference too? Its not modified anywhere... I dont like the foreach syntax because of the copy constraints... I never know how man thousand arrays I will have to process. – wadkar Aug 05 '11 at 13:52
  • @sudhi: i've already noticed the id issue which was fixed 7 minutes ago. thanks for pointing out the wrong place of the reference operator. as for passing the array itself as reference: try for yourself, it's hard to say beforehand, but i don't think there will be too much of an overhead (copy-on-write?) – knittl Aug 05 '11 at 13:55
  • hmm... copy-on-write, i always forget that, and my instincts tell me not to create copies of things that you are not going to change. PHP is very different in that regard. Anyways I don't have the time and necessary skills to rationalize the difference between passing by reference and passing by copy, so I will keep it simple and follow what you prescribed. Thanks a lot @knittl – wadkar Aug 05 '11 at 16:55
1
$max = array_reduce($data, function($a, $b) {    
  if (is_null($a)) return $b;
  return max($a['rating1'],$a['rating2'])>max($b['rating1'],$b['rating2']) ? $a : $b;
});

Result: no Entries $max= NULL otherwise $max['id'] is the id with the max rating

Alternatively this generic code

$max = array_reduce($data, function($a, $b) {
   if (is_null($a)) return $b;
   return maxRating($a)>maxRating($b) ? $a : $b;
 });

function maxRating($row){
   return (max(array_intersect_key($row,array_flip(array_filter(array_keys($row),function ($item) { return strstr($item,'rating')!==FALSE;})))));
}

Will find for all ratings of the form rating?

EDIT -- The code was trying to answer Q1 here is the answer for just Q2

$max = array_reduce($data, function($a, $b) {    
  if (is_null($a)) return $b;
  return $a['rating1']>$b['rating1'] ? $a : $b;
});

EDIT2 -- This is a generic solution for any number of rating? columns

$ratingKeys=array_filter(array_keys($data[0]),function ($item) { return strstr($item,'rating')!==FALSE;});

$max = array_reduce($data,function($a,$b) use (&$ratingKeys) {
  if (is_null($a)) {
    $a=array();
    foreach($ratingKeys as $key) {
      $a[$key]=$b[$key];
      $a[$key.'_id'] = $b['id'];
     }
     return $a;
  }
  foreach($ratingKeys as $key) {
    if ($a[$key]<$b[$key]) {
      $a[$key]=$b[$key];
      $a[$key.'_id']=$b['id'];
    }
  }
  return $a;
});

This code results in

array(4) {
  ["rating1"]=> float(0.0002788640872546)
  ["rating1_id"]=> int(1380)
  ["rating2"]=> float(2.2388295595073)
  ["rating2_id"]=> int(1379)
}

EDIT 3 -- If you change the format of the input array to use id as the array key, you can massively simplify

$max=array_reduce(array_keys($data),function ($a,$b) use (&$data) {
  if (is_null($a)) $a=array();
    foreach(array_keys($data[$b]) as $item) {
      if (!isset($a[$item]) {
        $a[$item] = $b;
      } else {
        if ($data[$a[$item]][$item]) < $data[$b][$item]) $a[$item]=$b;
      }
    return $a;
  }
});

This code results in

array(2) {
  ["rating1"]=> int(1380)
  ["rating2"]=> int(1379)
}
Bob Vale
  • 18,094
  • 1
  • 42
  • 49
  • I want to compare rating1 of each `$data[]` and not between each `$data[][id]` ! So there are three `max` and three corresponding `id`s – wadkar Aug 05 '11 at 13:18
  • This finds the row that has the maximum value in either rating1 or rating2 – Bob Vale Aug 05 '11 at 13:26
  • ohhk, maybe its still not clear to you, i want to find max in rating1 alone. ignore rating2, now how can i get id as well? – wadkar Aug 05 '11 at 13:28
  • I have provided rating1 alone, $max will equal the matching row so you can then just use $max['id'] to get the id – Bob Vale Aug 05 '11 at 13:34
  • I have no provided a completely generic solution for any number of rating columns – Bob Vale Aug 05 '11 at 13:51
  • awesome !! sorry I was focusing more on the answer above, let me look at your generic solution. I don't want the numeric value of [rating?], can we drop the parts where its added to the array? – wadkar Aug 05 '11 at 13:55
  • You need the parts included in the array so it knows to calculate them but you could use `$max=array_intersect_key($max,array_flip(array_filter(array_keys($max),function ($item) { return strstr($item,'_id')!==FALSE;}))));` on the result to reduce it down. A simpler solution would be to use id as the row keys, I'll update with an additional edit. – Bob Vale Aug 06 '11 at 07:23
  • Hmm, looking at the last edit (with ID as row keys) I think I better fix my code generating this array and get that ID as row key, (I will have 'id' in the second dimension as well, just in case I decide to insert the rows directly into the db) it'll indeed simplify lot of my problems, let me try and get back to you (pro'lly on Monday, when I get back to office) – wadkar Aug 06 '11 at 15:23
  • Kind apologies, I tried implementing your suggestion, but the other part of the code has too many instances of $data[$i] and I broke my code several times trying to change it to $data[$id]. Thanks to revision control, I am back to first answer. I will definitely keep your suggestion in mind next time I create array of arrays: Use the primary key of row as first dimension key. Thanks again! – wadkar Aug 08 '11 at 09:16