2
\$\begingroup\$

I wrote this algorithm but i was wondering if there was any way i can make it less 'expensive'. The algorithm needs to transform this data structure:

const deliveryHours = {
  monday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 6,
        minute: 0,
      },
    },
  ],
  tuesday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 6,
        minute: 0,
      },
    },
    {
      start: {
        hour: 5,
        minute: 0,
      },
      end: {
        hour: 3,
        minute: 0,
      },
    },
  ],
  wednesday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 4,
        minute: 0,
      },
    },
    {
      start: {
        hour: 5,
        minute: 0,
      },
      end: {
        hour: 1,
        minute: 0,
      },
    },
  ],
}

to:

[ { hours: { from: 3, to: 4 }, wednesday: true },
  { hours: { from: 3, to: 6 }, tuesday: true, monday: true },
  { hours: { from: 5, to: 1 }, wednesday: true },
  { hours: { from: 5, to: 3 }, tuesday: true } ]

This is my attempt, it is clearly not the best way to go about it but im having a hard time finding another way:

const deliveryHours = {
  monday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 6,
        minute: 0,
      },
    },
  ],
  tuesday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 6,
        minute: 0,
      },
    },
    {
      start: {
        hour: 5,
        minute: 0,
      },
      end: {
        hour: 3,
        minute: 0,
      },
    },
  ],
  wednesday: [
    {
      start: {
        hour: 3,
        minute: 0,
      },
      end: {
        hour: 4,
        minute: 0,
      },
    },
    {
      start: {
        hour: 5,
        minute: 0,
      },
      end: {
        hour: 1,
        minute: 0,
      },
    },
  ],
}



const mapDeliveryHours = (deliveryHours) => {
  const result = {};
  for (let i = 0; i < Object.keys(deliveryHours).length; i += 1) {
    const day = Object.keys(deliveryHours)[i];

    const ranges = deliveryHours[day];

    for (let j = 0; j < ranges.length; j++) {
      const range = ranges[j];

      result[range.start.hour.toString() + range.end.hour.toString()] = {
        hours: {
          from: range.start.hour,
          to: range.end.hour,
        },
        [day]: true,
      };

      for (let k = 0; k < Object.keys(deliveryHours).length; k++) {
        const innerDay = Object.keys(deliveryHours)[k];
        const innerRanges = deliveryHours[innerDay];

        for (let l = 0; l < innerRanges.length; l++) {
          const innerRange = innerRanges[l];
          if (day !== innerDay
            && innerRange.start.hour === range.start.hour
            && innerRange.end.hour === range.end.hour
          ) {
            result[range.start.hour.toString() + range.end.hour.toString()] = {
              ...result[range.start.hour.toString() + range.end.hour.toString()],
              [innerDay]: true,
            };
          }
        }
      }
    }
  }
  return Object.values(result);
};

console.log(mapDeliveryHours(deliveryHours))

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

It is not much less code but I believe it is clearer and easier to reason about:

function forEach(target, fn) {
    var keys = Object.keys(target);
    var key;
    var i = -1;
    while (++i < keys.length) {
        key = keys[i];
        fn(target[key], key);
    }
}
function transform(target, fn, accumulator) {
    if (accumulator === undefined) {
        accumulator = Object.create(target);
    }
    forEach(target, function (value, key) {
        return fn(accumulator, value, key);
    });
    return accumulator;
}
function aggregateHours(hours) {
    var agg = [];
    return transform(hours, function (result, spans, day) {
        spans.forEach(function (span) {
            var found = agg.find(function (el, i) { return el.hours.from === span.start.hour && el.hours.to === span.end.hour; });
            if (found) {
                found[day] = true;
            }
            else {
                agg.push({ hours: { from: span.start.hour, to: span.end.hour } });
                agg[agg.length - 1][day] = true;
            }
        });
    }, agg);
}

aggregateHours(deliveryHours);

Basically I have stolen the foreach and transform from my library: goodcore but written it all in javascript here so that you don't have to use it.

\$\endgroup\$
2
  • \$\begingroup\$ Awesome! It is definitely clearer than my attempt. \$\endgroup\$
    – milesr
    Commented Sep 7, 2018 at 9:59
  • \$\begingroup\$ Here is the fiddle to another possible solution: jsfiddle.net/0yhxp4t6/6 \$\endgroup\$
    – milesr
    Commented Sep 7, 2018 at 23:13
0
\$\begingroup\$

One to many, indexed data

When you need data organised so that there is a one to many relationship from an index to the data you can use a Map to do the hard work of ensuring that the one to many rule stays true.

A map holds data that is referenced via a unique index (or key as JS people like to call it). The data to hold will be an object containing named days, and the times of open and close.

Transform delivery times object to array

In this case the index is the opening time and the data is the days that have that time open.

You will need to create a index key that uniquely identifies a specific opening time. A simple string will do index = start + "-" end;

Use the index to query the map for an object associated.

If no object for that index, create a new object that contains the day and additional information pertaining to the index, then add it to the map using the index as the key.

If the index object exists then just add the open day to the object.

Once you have collected the data you then extract the values from the map to the data type you want. In this case an array, one item of each unique index (opening time)

Example

The function will look like

function transformDeliveryTimes(data) {
    const hours = new Map();

    const addOpen = (time, day) => {
        const from = time.start.hour, to = time.end.hour;
        const index = from + "-" + to;
        const open = hours.get(index);
        if (open) { open[day] = true }
        else { hours.set(index, {hours : {from, to}, [day] : true}) }
    }

    for (const [day, times] of Object.entries(data)) {
        for (const time of times) { addOpen(time, day) }
    }
    return [...hours.values()];
}
const openTimesArray = transformDeliveryTimes(deliveryHours);

Sort the result

The above creates an array that is similar but the order is not the same. If you need it sorted from earliest longest to latest longest you can sort the return array as follows

openTimesArray.sort((a,b)=> {
    const dif = a.hours.from - b.hours.from;
    return dif ? dif : a.hours.to - b.hours.to;
});
\$\endgroup\$
6
  • \$\begingroup\$ Nice use of destructuring over entries. I have to play with that a bit. :) Using a map is nice but couldnt you just keep an array parallell and simply use the map to find the objects if performance is the goal for large datasets in order to keep order? \$\endgroup\$
    – JGoodgive
    Commented Sep 8, 2018 at 8:29
  • \$\begingroup\$ @JGoodgive Map uses a hash table so there is no iteration overhead associated due to searching for the key when inserting or retrieving items. \$\endgroup\$
    – Blindman67
    Commented Sep 8, 2018 at 10:08
  • \$\begingroup\$ i know that but so does a pojo by implementation in most browsers. Last i benched Map VS Object, Object won. It might have changed but as of now i believe the largest win is semantic more than performance. \$\endgroup\$
    – JGoodgive
    Commented Sep 8, 2018 at 10:49
  • \$\begingroup\$ Also intermally we have to compare hashkey generation time vs ref compare and iteration. In The cases of small arrays and ref comparison they often end up faster when small (<50) in my experience. But like always in js, the compilers just keep improving. \$\endgroup\$
    – JGoodgive
    Commented Sep 8, 2018 at 10:52
  • \$\begingroup\$ jsperf.com/objecttransformation \$\endgroup\$
    – JGoodgive
    Commented Sep 8, 2018 at 11:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.