I am trying to compact an expression of individual days into a shorter expression including hyphen-sep开发者_C百科arate ranges.
Examples:
mon,tue,wed,thu,fri,sat
to be:mon-sat
mon,tue,wed,fri,sat
to bemon-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?
Basically, I would approach this by:
- Converting the days to corresponding numeric values
- Turning the array of numbers into a string with ranges
- 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 />';
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);
}
Define a lookup as a constant to easily determine the sequential position of each day.
Explode the string on commas and iterate the day values.
If the result string is empty, add the day with no delimiter/glue.
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.
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
)
);
}
精彩评论