Group rows of a 2d array by year-month value of date column and count occurrences in each group

57 Views Asked by At

So I have this code:

$avaDates = [
    ['date_starts' => '2024-03-01'],
    ['date_starts' => '2024-03-09'],
    ['date_starts' => '2024-04-05'],
    ['date_starts' => '2024-04-09'],
    ['date_starts' => '2024-04-15'],
    ['date_starts' => '2024-05-03']
];
$sum = 0;
$months = '';
foreach ($avaDates as $date) {
    $monthCheck = substr($date['date_starts'], 0, -3);
    if ($months !== $monthCheck) {
        $months = $monthCheck;
        $dateFormat = date("F-Y", strtotime($months));

        echo strtolower($dateFormat) . ' ' . $sum . "\n";

        $sum = 0;
    }
    $sum++;
}

the actual output is this:

march-2024 0
april-2024 2
may-2024 3

I'm looking for this output:

march-2024 2
april-2024 3
may-2024 1

I can't seem to make the logic work. Here is a link to the sandbox to run my example: https://onlinephp.io/c/47724

4

There are 4 best solutions below

0
Fermin Perdomo On BEST ANSWER

You need to do group and count and you can do something like this to get:

$avaDates = [['date_starts'=>'2024-03-01'],['date_starts'=>'2024-03-09'],['date_starts'=>'2024-04-05'],['date_starts'=>'2024-04-09'],['date_starts'=>'2024-04-15'],['date_starts'=>'2024-05-03']];

$groupDates = array_reduce($avaDates, function ($months, $date) {
    $dateFormat = date("F-Y", strtotime($date['date_starts']));
    if (isset($months[$dateFormat])) {
        $months[$dateFormat]++;
    } else {
        $months[$dateFormat] = 1;
    }
    return $months;
});
print_r($groupDates);

The array reduce function allows you to iterate through an array and group at the same time and return a value and, in this case, a new array with group by month and its count.

to print the array you can do it by:

foreach ($groupDates as $month => $count ) {
 echo $month . '->' . $count . "\n";
}

You can find the code here: https://onlinephp.io/c/47724

0
user3783243 On

I think you can use array_count_values to ge the counts if you trim off the day value. Something like:

$avaDates = [['date_starts'=>'2024-03-01'],['date_starts'=>'2024-03-09'],['date_starts'=>'2024-04-05'],['date_starts'=>'2024-04-09'],['date_starts'=>'2024-04-15'],['date_starts'=>'2024-05-03']];
foreach($avaDates as $key => $date){
    $avaDates[$key]['date_starts'] = substr($date['date_starts'], 0, 7);
}
foreach(array_count_values(array_column($avaDates, 'date_starts')) as $date => $count){
    echo date('F-Y', strtotime($date . '-01')) . ' ' . $count . PHP_EOL;
}

should do it. May even be able to use array_map to shorten that.

0
hedfol On
  1. Turn the month sum echo into a function (print_month_sum()).
  2. At the first iteration assign $months with $monthCheck and add 1 to the sum (if it's time to print).
  3. At the last iteration:
    • Always print a sum at least once.
    • Add 1 to a sum before printing if the month didn't change.
    • Do an additional output if the month did change.
function print_month_sum($months, $sum) {
    $dateFormat = date("F-Y", strtotime($months));
    echo strtolower($dateFormat).' '.$sum."\n";
}

$avaDates = [['date_starts'=>'2024-03-01'],['date_starts'=>'2024-03-09'],['date_starts'=>'2024-04-05'],['date_starts'=>'2024-04-09'],['date_starts'=>'2024-04-15'],['date_starts'=>'2024-05-03']];
$sum = 0;
$months = '';
foreach ($avaDates as $i => $date) {
    $is_first = ($i === 0);
    $is_last = ($i === count($avaDates) - 1);
    $monthCheck = substr($date['date_starts'], 0, -3);
    if ($is_first) {
        $months = $monthCheck;
    }
    if($months !== $monthCheck || $is_last){
        $last_changed = ($months !== $monthCheck && $is_last);

        $sum += $is_first || ($is_last && !$last_changed) ? 1 : 0;
        print_month_sum($months, $sum);

        if ($last_changed) {
            print_month_sum($monthCheck, 1);
        }
        $months = $monthCheck;
        $sum = 0;
    }
    $sum ++;
}

But it would be safer to use an array functions as suggested in other answers.

0
mickmackusa On

I would separate the data preparation and the data presentation. (Demo)

$result = [];
foreach ($avaDates as ['date_starts' => $d]) {
    $FY = date("F-Y", strtotime($d));
    $result[$FY] = ($result[$FY] ?? 0) + 1;
}
foreach ($result as $fy => $count) {
    echo strtolower("$fy $count\n");
}

If your input data is guaranteed to be sorted, then you can print inside the same loop as you process the respective groups, but it involves a bit more clunky scripting. (Demo)

$cached = null;
$count = 0;
foreach($avaDates as ['date_starts' => $d]) {
    $FY = date("F-Y", strtotime($d));
    if ($cached !== $FY) {
        if ($cached !== null) {
            echo " $count\n";
            $count = 0;
        }
        echo strtolower($FY);
    }
    $cached = $FY;
    ++$count;
}
if ($count) {
    echo " $count";
}