Reputation: 315
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
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
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
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
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