Convert consecutive comma-separated days into hyphenated day ranges
Asked Answered
Z

3

1

I am trying to compact an expression of individual days into a shorter expression including hyphen-separate ranges.

Examples:

  • mon,tue,wed,thu,fri,sat
    to be:
    mon-sat
  • mon,tue,wed,fri,sat
    to be
    mon-wed,fri-sat

My coding attempt:

function dayrange($days){
    $days = explode(",", str_replace(" ","",$days));
    return reset($days) . "-" . end($days);
}

How can I shorten the multi-day expression so that consecutive days are merged into a range of days?

Zwieback answered 10/5, 2011 at 2:15 Comment(2)
Does it need to wrap on the week too? ie. if an input like fri,sat,sun,mon comes in do you expect fri-mon or fri-sun, sun-mon?Apropos
um the week wrapping thing isn't required. it would be a nice to have i suppose but not needed for this projectZwieback
S
3

Basically, I would approach this by:

  1. Converting the days to corresponding numeric values
  2. Turning the array of numbers into a string with ranges
  3. Converting the numbers in the string back into days of the week

I wrote some code to do that:

/**
 * Convert an array of numbers to a string containing ranges and single values
 * @param array $numbers an array of numbers
 * @return string
 */
function compressNumbers($numbers) {
    $result = array();
    sort($numbers);
    $previousValue = reset($numbers);
    $startValue = $previousValue;
    foreach ($numbers as $value) {
        if ($value > $previousValue + 1) {
            if ($startValue == $previousValue) {
                $result[] = $startValue;
            } else {        
                $result[] = $startValue . '-' . $previousValue;
            }
            $startValue = $value;
        }
        $previousValue = $value;
    }
    if ($startValue == $previousValue) {
        $result[] = $startValue;
    } else {        
        $result[] = $startValue . '-' . $previousValue;
    }
    return implode(',', $result);
}

/*
 * Creates an array with values the three letter representation for days of the 
 * week and keys the corresponding numeric representation.
 *
 * @return array
 */
function createLookupNumberToDay() {
    $date = strtotime('now');
    $lookup = array();
    for ($i = 1; $i <= 7; $i++) {
        $lookup[date('w', $date)] = date('D', $date);
        $date = strtotime('+1 day', $date);
    }
    return $lookup;
}

/*
 * Converts a string listing days separated by commas into 
 * an array with values the numeric value for the corresponding
 * day of the week.
 *
 * @param string $days
 * @return array
 */
function convertDaysToNumbers($days) {
    $result = array();
    $daysArray = explode(",", str_replace(" ","",$days));
    foreach ($daysArray as $day) {
        $result[] = date('w', strtotime($day));
    }
    return $result;
}

/*
 * Converts the numbers in a string to the corresponding 3-letter day of the
 * week abbreviation.
 *
 * @param string $string
 * @return string
 */
function convertNumbersToDays($string) {
    $lookup = createLookupNumberToDay();
    return str_replace(array_keys($lookup), $lookup, $string);
}

function convert($string) {
    return (convertNumbersToDays(compressNumbers(convertDaysToNumbers($string))));
}

echo convert('mon,tue,wed,thu,fri,sat');
echo '<br />';
echo convert('mon,tue,wed,sat');
echo '<br />';
Stylobate answered 10/5, 2011 at 5:11 Comment(0)
A
0

Haven't tested this, but should give you a good start. It handles week wrapping as well.

function dayrange($days){
    $wdays = array("mon","tue","wed","thu","fri","sat","sun");

    $indays = explode(",", str_replace(" ","",$days)); // expand the list to an array


    $retstr = array_shift($indays); // get the first date

    $curpos = array_search($retstr, $wdays);  // current position in the wdays array
    $intv = 0;    // interval between days to avoid mon-tue like output

    foreach($indays as $d) {
       if($d == $wdays[$curpos]) {
          $curpos = ($curpos++) % 7; // this will take care of wrapping.
          $intv++;
       } else {
           $retstr.= ($intv > 1 ? "-".$d:",".$d); // use appropriate join
           $intv = 0; // reset interval
       }
    }
    if($intv > 0) {   // if anything was left deal with the end.
        $retstr.= ($intv > 1 ? "-".$d:",".$d);
    } else {
        $retstr.= ",".$d;
    }
    return ($retstr);
}
Apropos answered 10/5, 2011 at 4:35 Comment(3)
codepad.org/UKCMaRLN ... just returned the same string i gave it? +1 for effortZwieback
@Zwieback a sympathy uv? That does not help researchers to identify good, working solutions. Quite the contrary, answers that do not work should have a negative score.Tricyclic
Indeed, this answer does not return the desired result.Tricyclic
T
0
  1. Define a lookup as a constant to easily determine the sequential position of each day.

  2. Explode the string on commas and iterate the day values.

  3. If the result string is empty, add the day with no delimiter/glue.

  4. If the day is a consecutively positioned day, then potentially remove the appended yesterday substring if it was attached using a hyphen, then append a hyphen and the day.

  5. If the day is not a consecutively positioned day, then append a comma and the day.

Code: (Demo)

define('DAYS', array_flip(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']));

function condenseDays(string $days): string
{
    $result = '';
    foreach (explode(',', $days) as $day) {
        if (!$result) {
            $result .= $day;
        } elseif (DAYS[$day] === DAYS[$yesterday] + 1) {
            $result = str_replace("-$yesterday", '', $result) . "-$day";
        } else {
            $result .= ",$day";
        }
        $yesterday = $day;
    }
    return $result;
}
echo condenseDays('mon,tue,wed,thu,fri,sat') . "\n";
echo condenseDays('tue,thu,fri,sun') . "\n";
echo condenseDays('mon,tue,wed,fri,sat,sun') . "\n";
echo condenseDays('mon,thu,sun') . "\n";
echo condenseDays('tue,wed,fri,sat') . "\n";
echo condenseDays('mon,wed,fri,sun') . "\n";
echo condenseDays('mon,tue,thu,fri,sat,sun');

Output:

mon-sat
tue,thu-fri,sun
mon-wed,fri-sun
mon,thu,sun
tue-wed,fri-sat
mon,wed,fri,sun
mon-tue,thu-sun

Alternatively, if you'd rather use a brute-force approach, you can replace commas to hyphens for all neighboring days, then use regex to remove the "guts" of multi-consecutive days.

Code: (Demo)

define(
    'PAIRS',
    [
        [
            'mon,tue',
            'tue,wed',
            'wed,thu',
            'thu,fri',
            'fri,sat',
            'sat,sun'
        ],
        [
            'mon-tue',
            'tue-wed',
            'wed-thu',
            'thu-fri',
            'fri-sat',
            'sat-sun'
        ]
    ]
);

function condenseDays(string $days): string
{
    return preg_replace(
               '/-\K[^,]+-/',
               '',
               str_replace(PAIRS[0], PAIRS[1], $days)
           );
}

Sneakiest / Least-intelligible version where range-worthy commas are identified by their neighboring letter instead of 3-letter days.

Code: (Demo)

function condenseDays(string $days): string
{
    return preg_replace(
               '/-\K[^,]+-/',
               '',
               str_replace(
                   ['n,t', 'e,w', 'd,t', 'u,f', 'i,s', 't,s'],
                   ['n-t', 'e-w', 'd-t', 'u-f', 'i-s', 't-s'],
                   $days
               )
           );
}
Tricyclic answered 29/8, 2022 at 13:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.