John Ryan
John Ryan

Reputation: 315

Get N top scores from a flat array and allow unlimited elements per qualifying score in the event of ties

I'm working on a leader board that pulls the top scorers into first, second, and third place based on points. Right now I'm working with a sorted array that looks like this (but could be of infinite length with infinite point values):

$scores = Array
    (
        ["bob"] => 20
        ["Jane"] => 20
        ["Jill"] => 15
        ["John"] => 10
        ["Jacob"] => 5
    )

I imagine I could use a simple slice or chunk, but I'd like to allow for ties, and ignore any points that don't fit into the top three places, like so:

$first = Array
    (
        ["bob"] => 20
        ["Jane"] => 20
    )

$second = Array
    (
        ["Jill"] => 15
    )

$third = Array
    (
        ["John"] => 10
    )

Upvotes: 2

Views: 4703

Answers (4)

mickmackusa
mickmackusa

Reputation: 47903

Sort by score then filter and group: Demo

$limit = 3;
arsort($array);
$result = [];
foreach ($array as $k => $v) {
    if (count($result) === $limit && !isset($result[$v])) {
        break;
    }
    $result[$v][$k] = $v;
}
var_export(array_values($result));

Filter and group then sort by score group: Demo

$limit = 3;
$result = [];
foreach ($array as $k => $v) {
    if (isset($result[$v])) {
        $result[$v][$k] = $v;
    } elseif (count($result) < $limit) {
        $result[$v][$k] = $v;
    } else {
        $min = min(array_keys($result));
        if ($v > $min) {
            $result[$v][$k] = $v;
            unset($result[$min]);
        }
    }
}
krsort($result);
var_export(array_values($result));

If your input array is already sorted with descending scores, you can omit the sorting calls in these scripts.

Upvotes: 0

spidEY
spidEY

Reputation: 944

Here's my go at it:

<?php
function array_split_value($array)
{
    $result = array();
    $indexes = array();

    foreach ($array as $key => $value)
    {
        if (!in_array($value, $indexes))
        {
            $indexes[] = $value;
            $result[] = array($key => $value);
        }
        else
        {
            $index_search = array_search($value, $indexes);
            $result[$index_search] = array_merge($result[$index_search], array($key => $value));
        }
    }

    return $result;
}

$scores = Array(
    'bob' => 20, 
    'Jane' => 20, 
    'Jill' => 15, 
    'John' => 10, 
    'Jacob' => 5
);

echo '<pre>';
print_r(array_split_value($scores));
echo '</pre>';
?>

Upvotes: 0

Cheery
Cheery

Reputation: 16214

  $arr = array(
      "Jacob" => 5,
      "bob" => 20,
      "Jane" => 20,
      "Jill" => 15,
      "John" => 10,
  );
  arsort($arr);
  $output = array();
  foreach($arr as $name=>$score)
  {
      $output[$score][$name] = $score;
      if (count($output)>3) 
      {
          array_pop($output); 
          break;
      }
  }
  $output = array_values($output); 
  var_dump($output);

$first will be in $output[0], $second in $output[1] and so on.. Code is limited to 3 first places.

ps: updated to deal with tie on the third place

Upvotes: 3

Matthew
Matthew

Reputation: 48294

I would do something like:

function chunk_top_n($scores, $limit)
{
  arsort($scores);
  $current_score = null;
  $rank = array();
  $n = 0;

  foreach ($scores as $person => $score)
  {
    if ($current_score != $score)
    {
      if ($n++ == $limit) break;
      $current_score = $score;
      $rank[] = array();
      $p = &$rank[$n - 1];
    }

    $p[$person] = $score;
  }

  return $rank;
}

It sorts the array, then creates numbered groups. It breaks as soon as the limit has been reached.

You can do it with less code if you use the score as the key of the array, but the benefit of the above approach is it creates the array exactly how you want it the first time through.

You could also pass $scores by reference if you don't mind the original getting sorted.

Upvotes: 1

Related Questions