public static function kthSmallest(array $numbers, int $k)
{
// If the numbers array is empty, or if k is out of bounds
// return null. Should it be an exception instead?
$n = count($numbers);
if (empty($numbers) || $k >= $n) {
return null;
}
// Reset the array key indexes because we don't know what might be passed in
$numbers = array_values($numbers);
// If the array is 5 elements or smaller, use quicksort and return the element of interest.
if ($n <= 5) {
sort($numbers);
return $numbers[$k];
}
// Otherwise, we are going to slice $numbers into 5-element slices
// and find the median of each.
$num_slices = ceil($n / 5);
for ($i = 0; $i < $num_slices; $i++) {
$median_array[] = self::median(array_slice($numbers, 5 * $i, 5));
}
// Then we find the median of the medians.
$median_of_medians = self::median($median_array);
// Next we walk the array and seperate it into values that are greater than or less than
// this "median of medians".
$lower_upper = self::splitAtValue($numbers, $median_of_medians);
$lower_number = count($lower_upper['lower']);
$upper_number = count($lower_upper['upper']);
$equal_number = $lower_upper['equal'];
// Lastly, we find which group of values our value of interest is in, and find it in the
// smaller array.
if ($k < $lower_number) {
return self::kthSmallest($lower_upper['lower'], $k);
} elseif ($k < $lower_number + $equal_number) {
return $median_of_medians;
} else {
return self::kthSmallest($lower_upper['upper'], $k - $lower_number - $equal_number);
}
}