Reduce array of month names by creating hyphenated expressions from consecutive months
Asked Answered
B

3

4

I have an array like:

Array
(
    [0] => Jan
    [1] => Feb
    [2] => Mar
    [3] => Apr
    [4] => May
    [5] => Jun
    [6] => Sep
    [7] => Oct
    [8] => Dec
)

I need to convert it to

Array
(
    [0] => "Jan - Jun"
    [1] => "Sep - Oct"
    [2] => "Dec"
)

The months will always be in order, but since the array is dynamic, I can't think of a good efficient way to this, other than converting each month to number with date_parse and then combine with the months around it! but I am really confused as to how to do this, any ideas?

Blatant answered 12/7, 2013 at 12:35 Comment(7)
It would help a lot to know how dynamic it actually is. Is it generally pr. 4 month you need to combine the array, or is it random?Determine
Can you give a bit more detail about the problem? Why are the ranges uneven in your example? Is there any rule as to how you are forming the ranges?Exponent
... andy maybe post the code that builds the arrayWinner
Where are you getting the parameters for the range from? A pair of dates?Landes
Without converting months to a number, you'll need something to tell you what contiguous months look like, so your solution would need an array of all of the months, then loop through your dynamic array checking against that list. Take a shot at it and show the code for feedback.Gilleod
The thing is, this is part of a larger system where the user will choose multiple date ranges, and I have to list out all months outside those ranges...so the listing is gonna be entirely random.Blatant
Related: Convert consecutive comma-separated days into hyphenated day rangesBrawny
I
2

How about something like this:

function findConsecutiveMonths(array $input) {
    // Utility list of all months
    static $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                           'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');

    $chunks = array();

    for ($i = 0; $i < 12; $i++) {
        // Wait until the $i-th month is contained in the array
        if (!in_array($months[$i], $input)) {
            continue;
        }

        // Find first consecutive month that is NOT contained in the array
        for ($j = $i + 1; $j < 12; $j++) {
            if (!in_array($months[$j], $input)) {
                break;
            }
        }

        // Chunk is from month $i to month $j - 1
        $chunks[] = ($i == $j - 1) ? $months[$i] : $months[$i] .' - '. $months[$j - 1];

        // We know that month $j is not contained in the array so we can set $i
        // to $j - the search for the next chunk is then continued with month
        // $j + 1 because $i is incremented after the following line
        $i = $j;
    }

    return $chunks;
}

Demo: http://codepad.viper-7.com/UfaNfH

Intendance answered 12/7, 2013 at 13:31 Comment(0)
G
0

This should do it:

function combine_months($months) {
    $all_months = array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
    $target = array();
    $start = null;
    for ($i = 0; $i<12; $i++) {
        if (!in_array($all_months[$i], $months) && $start != null) {
            if ($start == null) {
                $target[] = $all_months[$i-1];
            }
            else {
                $target[] = sprintf("%s - %s", $start, $all_months[$i-1]);
            }
            $start = null;
        }
        elseif ($start == null) {
            $start = $all_months[$i];
        }
    }
    if ($start != null) {
        $target[] = $start;
    }
    return $target;
}

$test = combine_months(array("Jan","Feb","Mar","Apr","May","Jun","Sep","Oct","Dec"));
var_dump($test);

It iterates all available months, remembering the first common month, adding to the target array when a month is missing.

Gumm answered 12/7, 2013 at 12:57 Comment(0)
B
0

As an adaptation of this answer of mine, use a lookup array and references to push consecutive month names into the correct element -- use strtok() and concatenation to ensure that the end of range is always updated correctly.

This snippet will perform well because it only uses one loop and there are no in_array() calls (one of PHP's slows forms of searching an array).

Code: (Demo)

$lookup = array_flip(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);

$months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Sep', 'Oct', 'Dec'];
$result = [];
foreach ($months as $month) {
    if (isset($ref) && $lookup[$month] === $lookup[$last] + 1) {
        $ref = strtok($ref, "-") . "-$month";
    } else {
        unset($ref);
        $ref = $month;
        $result[] = &$ref;
    }
    $last = $month;
}
var_export($result);

Or written differently to leverage $i instead of $last because the input array is indexed. (Demo)

$result = [];
foreach ($months as $i => $month) {
    if ($i && $lookup[$month] === $lookup[$months[$i - 1]] + 1) {
        $ref = strtok($ref, "-") . "-$month";
    } else {
        unset($ref);
        $ref = $month;
        $result[] = &$ref;
    }
}
var_export($result);

If your input array needs to accommodate circular referencing, use the modulus operator with 12 (the number of months in a year) to ensure that Jan "looks back" to Dec. (Demo)

if (isset($ref) && $lookup[$month] === ($lookup[$last] + 1) % 12) {
Brawny answered 6/5, 2023 at 3:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.