Saturday 31 August 2024

AskJS Toy problem

There was a post on Reddit last week that got me sort of excited (I probably need to get out more).

It's been removed now, but my RSS feed still has it:

Write a function that takes a string representing open hours and returns an object where the keys are days of the week and the values are the corresponding open hours. Missing days should still be represented as a key but with an empty string value.

Examples:

"Mon-Sun 11:00 am - 10 pm" would return:

{
  Mon: "11:00 am - 10:00 pm", 
  Tues: "11:00 am - 10:00 pm", 
  Wed: "11:00 am - 10:00 pm", 
  Thu: "11:00 am - 10:00 pm", 
  Fri: "11:00 am - 10:00 pm", 
  Sat: "11:00 am - 10:00 pm", 
  Sun: "11:00 am - 10:00 pm"
}

"Mon-Thu, Sat 11 am - 9 pm / Fri 5 pm - 9 pm" would return:

{
  Mon: "11:00 am - 9:00 pm",
  Tues: "11:00 am - 9:00 pm",
  Wed: "11:00 am - 9:00 pm",
  Thu: "11:00 am - 9:00 pm",
  Fri: "5:00 pm - 9:00 pm",
  Sat: 11:00 am - 9:00 pm",
  Sun: ""
}

Additional test cases:

  • "Mon-Sun 11 am - 12 am"
  • "Mon-Fri, Sat 11 am - 12 pm / Sun 11 am - 10 pm"
  • "Mon-Thu, Sun 11:30 am - 10 pm / Fri-Sat 11:30 am - 11 pm"
  • "Mon-Thu 11 am - 11 pm / Fri-Sat 11 am - 12:30 am / Sun 10 am - 11 pm"

This is what I came up with:

/*
 * Names regex groups are really handy! Remember to end a regex with a ";"!
 */
const timeRegex = /(?<sHour>\d{1,2})(\:(?<sMinute>\d{1,2}))?\s(?<sAmPm>am|pm)\s\-\s(?<eHour>\d{1,2})(\:(?<eMinute>\d{1,2}))?\s(?<eAmPm>am|pm)/;
const daysRegex = /\b([a-zA-Z]{3,4}\-?)\b/g;
/*
 * Our tests:
 */
const tests = [
  "Mon-Sun 11:00 am - 10 pm",
  "Mon-Sun 11 am - 12 am",
  "Mon-Thu, Sat 11 am - 9 pm / Fri 5 pm - 9 pm",
  "Mon-Fri, Sat 11 am - 12 pm / Sun 11 am - 10 pm",
  "Mon-Thu, Sun 11:30 am - 10 pm / Fri-Sat 11:30 am - 11 pm",
  "Mon-Thu 11 am - 11 pm / Fri-Sat 11 am - 12:30 am / Sun 10 am - 11 pm"
]
/*
 * Our original object
 */
const daysObj = { "Mon": "", "Tues": "", "Wed": "", "Thu": "", "Fri": "", "Sat": "", "Sun": "" }
/*
 * Our array of days from the object above
 */
const days = Object.keys(daysObj)
/*
 * Our function to format a time
 */
const formatTime = (hour, minute, AmPm) => `${hour}:${minute} ${AmPm}`
/*
 * Our function to format a time duration
 */
const generateTime = (sHour, sMinute, sAmPm, eHour, eMinute, eAmPm) => {
  const sTime = formatTime(sHour, sMinute, sAmPm)
  const eTime = formatTime(eHour, eMinute, eAmPm)
  return `${sTime} - ${eTime}`
}
/*
 * Iterate over the tests
 */
for (const test of tests) {
  /*
   * Start with a fresh version of the days object by copying the one from above.
   */
  const returnObj = Object.assign({}, daysObj)
  /*
   * First we need to chunks of the string seperated by " / "
   * We'll then iterate over each chunk.
   */
  for (chunk of test.split(" / ")) {
    /*
     * We'll then get all the days from the chunk.
     */
    const daysResult = chunk.match(daysRegex)
    /*
     * We'll then destructure the groups from the regex match. Named regex groups are really helpful for this as we
     * we can then access the values by name from the groups object, and have default values for those that might be 
     * undefined.
     */
    const { sHour, sMinute = '00', sAmPm, eHour, eMinute = '00', eAmPm } = timeRegex.exec(chunk.trim()).groups
    /*
     * We'll then generate the time from the values we've extracted from the regex match.
     */
    const timeString = generateTime(sHour, sMinute, sAmPm, eHour, eMinute, eAmPm)
    /*
     * We'll then iterate over the days we've extracted from the chunk.
     */
    for (let i = 0; i < daysResult.length; i++) {
      /*
       * If the last character of the day is a hyphen, we know we need to iterate over the days array we created above.
       */
      if (daysResult[i].charAt(daysResult[i].length - 1) === "-") {
        /*
         * We'll then iterate over the days array we created above and populate it with the time string we've generated.
         */
        for (let l = days.findIndex(e => e === daysResult[i].slice(0, -1)); l < days.length; l++) {
          if (days[l] !== daysResult[i + 1]) {
            returnObj[days[l]] = timeString
          } else {
            /*
             * If the current day isn't the same as the next day in the array, we'll break.
             */
            break;
          }
        }
      } else {
        /*
         * If the last character of the day is not a hyphen, we can just set taht day with the time string we've 
         * generated.
         */
        returnObj[daysResult[i]] = timeString
      }
    }
  }
  /*
   * Display the result alng with the test
   */
  console.log(test, JSON.stringify(returnObj, null, 2))
}

That was fun!

Notes:

Be aware that there's no error checking... because that wasn't in the requirements, though it should be easy enough to add. For instance, we need to consider:

  • In a range of days does the second have an index before the first?
  • Is the start time after the end time?
  • What should happen with duplicate days, such that an date range is then partially or totally replicated within another range, or a single day exists within a previous range?

No comments:

Post a Comment