user796443
user796443

Reputation:

Group array keys into number ranges, possible?

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)...

Upvotes: 7

Views: 3376

Answers (4)

ircmaxell
ircmaxell

Reputation: 165201

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) 
}

Upvotes: 8

HamZa
HamZa

Reputation: 14921

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
)

Upvotes: 1

nickb
nickb

Reputation: 59699

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); 
}));

Upvotes: 10

castis
castis

Reputation: 8223

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

Upvotes: 1

Related Questions