Преобразование последовательных дней, разделенных запятыми, в диапазоны дней через дефис

#php #days

#php #диапазон #синтаксический анализ текста #дни #перенос

Вопрос:

Я пытаюсь сжать выражение отдельных дней в более короткое выражение, включающее диапазоны, разделенные дефисом.

Примеры:

  • mon,tue,wed,thu,fri,sat
    чтобы быть:
    mon-sat
  • mon,tue,wed,fri,sat
    чтобы быть
    mon-wed,fri-sat

Моя попытка кодирования:

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

Как я могу сократить многодневное выражение, чтобы последовательные дни были объединены в диапазон дней?

Комментарии:

1. Нужно ли переносить и на неделю? ie. если вводятся данные типа пт, сб, вс, пн, вы ожидаете, что будет пт-пн или пт-вс, вс-пн?

2. хм, перенос недели не требуется. было бы неплохо иметь, я полагаю, но не нужно для этого проекта

Ответ №1:

В принципе, я бы подошел к этому следующим образом:

  1. Преобразование дней в соответствующие числовые значения
  2. Преобразование массива чисел в строку с диапазонами
  3. Преобразование чисел в строке обратно в дни недели

Я написал некоторый код для этого:

 /**
 * 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 />';
  

Ответ №2:

Не тестировал это, но должно дать вам хорошее начало. Он также обрабатывает перенос недели.

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

Комментарии:

1. codepad.org/UKCMaRLN … только что вернул ту же строку, которую я ей дал? 1 за усилия

2. @Jason a sympathy uv? Это не помогает исследователям находить хорошие, работающие решения. Напротив, ответы, которые не работают, должны иметь отрицательную оценку.

3. Действительно, этот ответ не возвращает желаемый результат .

Ответ №3:

  1. Определите поиск как константу, чтобы легко определить последовательное расположение каждого дня.

  2. Разбейте строку на запятые и повторите значения дня.

  3. Если результирующая строка пуста, добавьте день без разделителя / склейки.

  4. Если день является последовательно расположенным днем, то потенциально удалите добавленную подстроку yesterday, если она была присоединена с использованием дефиса, затем добавьте дефис и день.

  5. Если день не является последовательно расположенным днем, тогда добавьте запятую и день.

Код: (демо)

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

Вывод:

 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
  

В качестве альтернативы, если вы предпочитаете использовать метод грубой силы, вы можете заменить запятые на дефисы для всех соседних дней, а затем использовать regex для удаления «внутренностей» нескольких дней подряд.

Код: (демо)

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

Самая хитрая / наименее понятная версия, в которой запятые, соответствующие диапазону, обозначаются соседней буквой вместо дней из 3 букв.

Код: (демо)

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