Home » Php » php – Group array keys into number ranges, possible?

php – Group array keys into number ranges, possible?

Posted by: admin July 12, 2020 Leave a comment

Questions:

I have the following array:

(
    [25] => 1
    [26] => 3
    [10] => 2
    [24] => 1
)

It was created using the array_count_values() function in PHP.

Actual original array was something like this, before array_count_values…

Array
(
    [0] => 26
    [1] => 
    [2] => 18
    [3] => 28
    [4] => 22
    [5] => 21
    [6] => 26
    [7] => 
    [8] => 
    [9] => 
    [10] => 
    [11] => 
    [12] =>
    [13] =>
    [14] =>
    [15] =>
    [16] =>
    [17] =>
    [18] =>
    [19] =>
    [20] =>
)

These are ages, so how can I group these into age groups?

Lets say I want the following age groups: <= 18 19-26 27-32 > 32

It is supposed to look:

(
[<= 18] => 1
[19-26] => 4
[27-32] => 2
[ > 32] => 1
)

Is there a ready function for this?

My solution:
1 tedious way would be to create variables of age groups. Than foreach and increase variable ++ for specific age group if key matches range ($min <= $value) && ($value <= $max)

How to&Answers:

Interesting problem. Here is my solution. First, define an array of age ranges:

$age_ranges = array(
    array( 0, 18),
    array( 19, 26),
    array( 27, 32),
    array( 33, 150) // I use 150 as the "max" age
);

Then, we have your array_count_values() output:

$array_count_values = array( 25 => 1, 26 => 3, 10 => 2, 24 => 1); // From OP

Now, we create an array of all ages, where the keys are the age, and the values are the number of people with that age. It needs to be sorted by its keys for the next step.

$all_ages = $array_count_values + array_fill( 0, 150, 0);
ksort( $all_ages);

Finally, I loop over all the age ranges, slice off the age range from the $all_ages array, and sum their values to produce an array of the age ranges, with its values corresponding to how many people fell into that age range.

$result = array();
foreach( $age_ranges as $range) {
    list( $start, $end) = $range;
    $result["$start-$end"] = array_sum( array_slice( $all_ages, $start, $end - $start + 1));
}

A print_r( $result); yields the following output:

Array
(
    [0-18] => 2
    [19-26] => 5
    [27-32] => 0
    [33-150] => 0
)

Edit: Since you still have access to your original array, you can just calculate how many “unknowns” you had at the very end:

$result['unknown'] = count( array_filter( $original_array,  function( $el) { 
    return empty( $el); 
}));

Answer:

This sounds like a perfect case of map-reduce.

Map step: turn each age into one of the 4 ranges.

Reduce step: turn the range into a result array of ranges.

Let’s try it out:

$map = function($age) {
    if (!$age) {
        return 'unknown';
    } elseif ($age <= 18) {
        return '<= 18';
    } elseif ($age <= 26) {
        return '19-26';
    } elseif ($age <= 32) {
        return '26-32';
    }
    return '> 32';
}

So basically here, we’re just turning each age into a string representation of the range it represents. So now we’ll have an array of ranges, so we need to reduce that out to a summary (add up all of those ranges):

And then the reduce function:

$reduce = function($result, $age) {
    $result[$age]++;
    return $result;
}

This is quite simple. If you wanted to support dynamic ranges, then you’d have some logic in there to check if the age is not set (and then initialize it to 0)…

So now, putting it all together:

$array = array(12, 63, 24, 34, 12, 10, 19,); // Your ages
$data = array_map(function($age) {
    if (!$age) {
        return 'unknown';
    } elseif ($age <= 18) {
        return '<= 18';
    } elseif ($age <= 26) {
        return '19-26';
    } elseif ($age <= 32) {
        return '26-32';
    }
    return '> 32';
}, $array);
$result = array_reduce($data, function($result, $age) {
    $result[$age]++;
    return $result;
}, array('unknown' => 0, '<= 18' => 0, '19-26' => 0, '26-32' => 0, '> 32' => 0));

Which then yields:

array(4) { 
    ["unknown"]=> int(0)
    ["<= 18"]=> int(3) 
    ["19-26"]=> int(2) 
    ["26-32"]=> int(0) 
    ["> 32"]=> int(2) 
}

Answer:

If you wanted to just use the array you had before array_count_values(), you could use

$ages = array(
    0 => 26,
    1 => 18,
    2 => 28,
    3 => 22,
    4 => null,
    5 => 21,
    6 => 26,
    7 => null);

create a blank array to fill up

$final = array(
    'unknown' => 0,
    '18' => 0,
    '19_26' => 0,
    '27_32' => 0,
    '32' => 0,
);

array_walk($ages, function($age, $i, $final) {
    if (!$age) { $final['unknown']++; return; }
    if ($age <= 18) { $final['18']++; return; }
    if ($age <= 26) { $final['19_26']++; return; }
    if ($age <= 32) { $final['27_32']++; return; }
    if ($age > 32) { $final['32']++; return; }
}, &$final);

output from $final:

array (size=5)
  'unknown' => int 2
  18 => int 1
  '19_26' => int 4
  '27_32' => int 1
  32 => int 0

Answer:

After seeing all those solutions, i thought why not using Regex with the original array ?

$ages = array(25, 26, 10, 24, 10, 26, 26, 32, 32, 54, 84, 4, 18, 5, 98, 27);
$string = '#'. implode('#', $ages) . '#';
preg_match_all('/(#[0-9]#|#1[0-7]#)|(#1[8-9]#|#2[0-6]#)|(#2[7-9]#|#3[0-2]#)|(#3[3-9]#|#[4-9][0-9]#|#1[0-5][0-9]#)/', $string, $groups);
$age_ranges = array('0-17' => count(array_filter($groups[1])), '18-26' => count(array_filter($groups[2])), '27-32'
 => count(array_filter($groups[3])), '33-159' => count(array_filter($groups[4])));

print_r($age_ranges);

Output:

Array
(
    [0-17] => 2
    [18-26] => 3
    [27-32] => 1
    [33-159] => 2
)