Luca
Luca

Reputation: 135

Javascript: how to manipulate an array of time ranges

I have an array of time ranges (in string format) like this:

["09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00", "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00", "16:00 - 16:30", "17:00 - 17:30"]

How to get a new array (or manipulating the starting one) that contains:

The result of the above example must be:

["09:00 - 11:00", "11:30 - 13:00", "16:00 - 16:30", "17:00 - 17:30"]

My attempt:

let start = ["09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00", "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00", "16:00 - 16:30", "17:00 - 17:30"]
let compressed = [];
for (let i = 0; i < start.length; i++) {
    let currentSlot = start[i];
    let nextSlot = start[i + 1];
    if (currentSlot && nextSlot) {
      let currentSlotStartHour = currentSlot.slice(0, 5);
      let currentSlotEndHour = currentSlot.slice(-5);
      let nextSlotStartHour = nextSlot.slice(0, 5);
      let nextSlotEndHour = nextSlot.slice(-5);
      if (currentSlotEndHour === nextSlotStartHour) {
        // merge slots
        console.log(currentSlotStartHour);
        console.log(currentSlotEndHour);
        console.log(nextSlotStartHour);
        console.log(nextSlotEndHour);
        let compressedSlot = currentSlotStartHour
          .concat(" - ")
          .concat(nextSlotEndHour);
        // add compressed slot
        compressed.push(compressedSlot);
        // remove the next slot
        result.splice(i + 1, 1);
      } else {
        compressed.push(currentSlot);
      }
    }
}
console.log(compressed);
// compressed is: ["09:00 - 10:00", "10:00 - 11:00", "11:30 - 12:30", "12:30 - 13:00", "16:00 - 16:30"]

Thanks

Upvotes: 0

Views: 253

Answers (2)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48630

The most basic algorithm is the following:

  1. If the accumulator is empty, push the current time slot
  2. If it is not empty, compare the last known time slot's end time to the current start time
  3. If they are:
    1. not equal, add a new slot to the accumulator
    2. else, update the last known time slot's end time with the current end time

const slots = [
  "09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00",
  "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00",
  "16:00 - 16:30",
  "17:00 - 17:30"
];

const optimizeTimeSlots = (slots, delimiter) => {
  return slots.reduce((acc, slot) => {
    if (acc.length === 0) {
      acc.push(slot);
    } else {
      const curr = slot.split(delimiter);
      const prev = acc[acc.length - 1].split(delimiter);
      if (prev[1] !== curr[0]) {
        acc.push(slot);
      } else {
        prev[1] = curr[1];
        acc[acc.length - 1] = prev.join(delimiter);
      }
    }
    return acc;
  }, []);
}

console.log(optimizeTimeSlots(slots, ' - '));
.as-console-wrapper { top: 0; max-height: 100% !important; }

You can remove the main branching-logic by removing the following if (acc.length === 0) / else clause and start your accumulator with [ slots.shift() ] instead of an empty array.

Alternatives

You could modify this slightly, to make it look more ES6-ish...

const slots = [
  "09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00",
  "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00",
  "16:00 - 16:30",
  "17:00 - 17:30"
];

const optimize = (arr, delim) =>
  arr.reduce((acc, item) =>
    (([cs, ce, ps, pe]) => pe !== cs
      ? [ ...acc, ...[[ps, pe], [cs, ce]].map(p => p.join(delim)) ]
      : [ ...acc, [ ps, ce ].join(delim) ]
    )([item, acc.pop()].map(p => p.split(delim)).flat()),
    [ arr.shift() ]);


console.log(optimize(slots, ' - '));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Here is an optimized version that splits each slot once and recombines them at the very end. This is much harder to follow.

const slots = [
  "09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00",
  "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00",
  "16:00 - 16:30",
  "17:00 - 17:30"
];

const optimize = (arr, delim) =>
  arr.reduce((acc, item) =>
    ((prev, [ start, end ]) => prev.end !== start
      ? [ ...acc, prev, { start, end } ]
      : [ ...acc, { start: prev.start, end } ]
    )(acc.pop(), item.split(delim)),
    [ (([start, end]) => ({ start, end }))
      (arr.shift().split(delim)) ])
    .map(obj => Object.values(obj).join(delim));


console.log(optimize(slots, ' - '));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Upvotes: 3

mplungjan
mplungjan

Reputation: 178094

I got held up before I could post

This version is using objects which are easier to use subsequently

const compressed = ["09:00 - 09:30", "09:30 - 10:00", "10:00 - 10:30", "10:30 - 11:00", "11:30 - 12:00", "12:00 - 12:30", "12:30 - 13:00", "16:00 - 16:30", "17:00 - 17:30"].reduce((acc, cur) => {
  const [start, end] = cur.split(" - ");
  if (acc.length) {
    if (acc[acc.length - 1].end === start) acc[acc.length - 1].end = end;
    else acc.push({ start, end });
  } else acc.push({ start, end });
  return acc;
}, [])
console.log(compressed)

Upvotes: 1

Related Questions