1

Whats the best solution to take an array with 100 elements (all numbers) and reduce the array size to a smaller number of elements averaging the in between/combined numbers into new. I do NOT mean slice or crop.

Example:

$array = [10,20,30,40,50,60];
$final_count = 3;
$new_array = array_slimmer($array, $final_count);

Output: [15,35,55]

Kyle Anderson
  • 1,820
  • 5
  • 21
  • 29
  • I believe you'd have to specify some more rules. What about an odd number of elements, what would be the desired result there? What about unsorted arrays, should they be sorted first or not? – El_Vanja Apr 05 '20 at 22:25
  • $final_count can be anything less than total count. array should not be sorted. Imagine stock market volumes over time. 300 days is too many so lets show 100 instead. Or whatever data you want to use. – Kyle Anderson Apr 05 '20 at 22:41
  • So you're looking to sort of... "group" neighboring elements and extract their average, if I understand this correctly? – El_Vanja Apr 05 '20 at 22:47
  • Explain case `count = 5 & final_count = 2`, for ex. – Aksen P Apr 05 '20 at 22:47

2 Answers2

3

Here's a solution based on an existing answer about breaking an array into a set number of chunks:

$array = [11, 3, 45, 6, 61, 89, 22];

function array_slimmer(array $array, int $finalCount): array
{
    // no work to be done if we're asking for equal or more than what the array holds
    // same goes if we're asking for just one array or (nonsensical) less
    if ($finalCount >= count($array) || $finalCount < 2) {
        return $array;
    }
    return array_map(function (array $chunk) {
        // rounded to two decimals, but you can modify to accommodate your needs
        return round(array_sum($chunk) / count($chunk), 2);
    }, custom_chunk($array, $finalCount));
}

// this function is from the linked answer
function custom_chunk($array, $maxrows) {
    $size = sizeof($array);
    $columns = ceil($size / $maxrows);
    $fullrows = $size - ($columns - 1) * $maxrows;

    for ($i = 0; $i < $maxrows; ++$i) {
        $result[] = array_splice($array, 0, ($i < $fullrows ? $columns : $columns - 1));
    }
    return $result;
}

print_r(array_slimmer($array, 2));
print_r(array_slimmer($array, 3));
print_r(array_slimmer($array, 4));

This outputs:

Array ( [0] => 16.25 [1] => 57.33 ) 
Array ( [0] => 19.67 [1] => 33.5 [2] => 55.5 )
Array ( [0] => 7 [1] => 25.5 [2] => 75 [3] => 22 )

Demo

El_Vanja
  • 3,660
  • 4
  • 18
  • 21
  • Wonderful example, thank you for sharing. Worked perfect for a sparkline generator. – Josh Jan 18 '23 at 15:50
2

You could use array_chunk to split your array into pieces, the size being count($array) / $final_count, then use array_map to take the average value from each of those chunks (array_sum($chunk) / count($chunk)):

$array = [10,20,30,40,50,60];
$final_count = 3;

$new_array = array_map(function ($a) {
    return array_sum($a) / count($a);
}, array_chunk($array, (int)(count($array) / $final_count)));

print_r($new_array);

Output

Array
(
    [0] => 15
    [1] => 35
    [2] => 55
)

Demo on 3v4l.org

Note

This will give extra values on the end if the array length is not evenly divisible by $final_count (demo). You will need to pad the array out in that case to a multiple of that length using code such as this, which replicates the last value in the array:

while (count($array) % $final_count != 0) {
    $array[] = end($array);
}

Demo on 3v4l.org

Nick
  • 138,499
  • 22
  • 57
  • 95
  • This gives a nice result for the example in the question, but isn't [failsafe](https://3v4l.org/LcHA6). – El_Vanja Apr 05 '20 at 23:12
  • @El_Vanja thanks for pointing that out. It can be resolved by filling the array as I describe in the latter part of the answer. – Nick Apr 05 '20 at 23:15