Home > Mobile >  How does one reduce a list of string-type workday data-items (date, working hours) into a list of wo
How does one reduce a list of string-type workday data-items (date, working hours) into a list of wo

Time:11-20

I have an Array of Dates that I'm trying to concatenate to make a date range in javascript.

[Update]: What I've been attempted so far is splitting each month/day/year/hours into their own arrays, performing functionality to get the day range, (trying) to splice out the months so they would line up with the updated concatenated day range and then lastly needing to then add all of the hours for the days that were concatenated.

So the starting array:

["Nov 23 2021 8 hrs", "Nov 24 2021 8 hrs", "Nov 27 2021 8 hrs", "Dec 3 2021 8 hrs"]

Should be:

["Nov 23-24 2021 16 hrs", "Nov 27 2021 8 hrs", "Dec 3 2021 8 hrs"]

I am able to successfully split out the array indexes, push the date numbers into a new array, concatenate those, but im having trouble figuring out how to get rid of the extra month indexes based on any date after the first one.

heres the code for the day concatenation:

const getDayRanges = (array) => {
  var ranges = [],
    rstart,
    rend;
  for (var i = 0; i < array.length; i  ) {
    rstart = array[i];
    rend = rstart;
    while (array[i   1] - array[i] == 1) {
      rend = array[i   1]; // increment the index if the numbers sequential
      i  ;
    }
    ranges.push(rstart == rend ? rstart   "" : rstart   " to "   rend);
  }
  return ranges;
};

I'm guessing im going to need to either pass in the whole array and have it specifically look for array[i] in a nested for loop, or perhaps add a new parameter to that function for months/years etc to compare the indexes? Im a little stuck on how to do that specifically though.

CodePudding user response:

I think the logic of keeping dates with hours is flawed. If you keep the hours separately, like in a Map, or in a two-dimensional array, it would be much easier to achieve what you want, with Date.parse and new Date, you can check the dates. Something like this:

let date = new Date(Date.parse('Nov 23 2021')) 
let date1 = new Date(Date.parse('Nov 24 2021')) 
//and use Date methods to compare them

Otherwise you have to create an algorithm to match regular expression in order to find the date and hours, keep them, compare them, combine them, and add them to a new list.

CodePudding user response:

This is a classic map/reduce task.

Following steps have to be taken ...

  1. map the raw input data into a convenient working format and sort each item by date (and hours).

  2. After mapping, reduce the data-items by trying to find ranges of consecutive (or same) days.

  3. After the initial map/reduce task (steps 1 2), map again; this time the reduced range-item data into either a special stringified range representation or into the original input format.

In addition ...

function createWorkDayItem(record) {
  const regXWorkDayData
    = /(?<date>[A-Za-z] \s \d{1,2}\s \d{4})\s (?<hours>\d (?:\.\d )?)\s hrs/;
  const {
    date = null,
    hours = null,
  } = regXWorkDayData.exec(record)?.groups ?? {};

  return {
    record,
    hours: (hours !== null) ? Number(hours) : hours,
    date,
    time: (date !== null) ? new Date(date).getTime() : date,
  };
}
function compareWorkDayItems(a, b) {
  return a.time - b.time || b.hours - a.hours;
}

function collectItemOfSameOrConsecutiveDay(listOfRanges, currentItem, idx, arr) {
  if (idx >= 1) {
    const recentRange = listOfRanges.at(-1);
    const recentRangeItem = recentRange.at(-1);

    const deltaT = Number(
      ((currentItem.time - recentRangeItem.time) / 1000 /60 / 60 / 24) // msec => day(s)
        .toFixed(4)
    );
    if (deltaT <= 1/* day*/) {

      recentRange.push(currentItem)
    } else {
      listOfRanges.push([currentItem]);
    }
  } else {
    listOfRanges.push([currentItem]);
  }
  return listOfRanges;
}

function createWorkDayRangeFormat(rangeItem) {
  let result;
  if (rangeItem.length === 1) {

    result = rangeItem[0].record;
  } else {
    const totalHours = rangeItem
      .reduce((total, { hours }) => total   hours, 0);

    const [monthStart, dayStart, yearStart]
      = rangeItem[0].date.split(/\s /);

    const [monthEnd, dayEnd, yearEnd]
      = rangeItem.at(-1).date.split(/\s /);

    if (yearStart === yearEnd) {
      if (monthStart === monthEnd) {
        result = [

          monthStart,
          [dayStart, dayEnd].join('-'),
          yearStart,

        ].join(' ');
      } else {
        result = [[

          [monthStart, dayStart].join(' '),
          [monthEnd, dayEnd].join(' '),

        ].join(' - '), yearEnd].join(' ');
      }
    } else {
      result = [

        [monthStart, dayStart, yearStart].join(' '),
        [monthEnd, dayEnd, yearEnd].join(' '),

      ].join(' - ')
    }
    result = [result, totalHours, 'hrs'].join(' ');
  }
  return result;
}

const workDayHourList = [
  "Nov 23 2021 8 hrs", 
  "Nov 24 2021 4.5 hrs", 
  "Nov 24 2021 2.5 hrs",

  "Nov 27 2021 8 hrs",
  "Dec 3 2021 8 hrs",
  
  "Dec 16 2021 2.25 hrs",
  "Dec 18 2021 7.75 hrs",
  "Dec 17 2021 6.5 hrs",

  "Dec 1 2021 4 hrs",
  "Nov 30 2021 6 hrs",

  "Dec 31 2020 3.5 hrs",
  "Dec 30 2020 4.25 hrs",
  "Jan 02 2021 3.75 hrs",
  "Jan 01 2021 2.25 hrs",
];

console.log(
  '1st ... map the raw input data and sort it by date and hours ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
);
console.log(
  '2nd ...after mapping, reduce the data by trying to find ranges of consecutive days ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
    .reduce(collectItemOfSameOrConsecutiveDay, [])
);
console.log(
  '3rd ... after initial map/reduce, map again, the reduced (range) date into either a special range or into the original input format ...',
  workDayHourList
    .map(createWorkDayItem)
    .sort(compareWorkDayItems)
    .reduce(collectItemOfSameOrConsecutiveDay, [])
    .map(createWorkDayRangeFormat)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related