0

I have one array of dates, and one array of objects. Each have a date property. I need to cumulatively sort the objects by date. So, for every date in the date array, I would like to create a cumulative object with every object who's date property is previous to the date in the date array.

For example, the following date array and object array:

['2017-11-5', '2018-3-1', '2018-3-22']

[{name: 'Jes', date: '2017-11-2'}, {name: 'Jill', date: '2018-1-5'}, {name: 'Joe', date: '2018-2-25'}, {name: 'Jack', date: '2018-3-21'}]

The desired output would be:

[{name: 'Jes', date: '2017-11-2'}]

[{name: 'Jes', date: '2017-11-2'}, {name: 'Jill', date: '2018-1-5'}, {name: 'Joe', date: '2018-2-25'}]

[{name: 'Jes', date: '2017-11-2'}, {name: 'Jill', date: '2018-1-5'}, {name: 'Joe', date: '2018-2-25'}, {name: 'Jack', date: '2018-3-21'}]

I am trying to do this with approximately 500 dates and 30,000 objects.

Here is a current code snippet, but I am having issues with performance, due to the number of objects I am iterating through.

    _.each(dtArray,function(i:Date){
  let dt = new Date(i);
  let filtered = _.filter(data,function(row){
    let dtVal = new Date(row['date']);
    return dtVal<=dt;
  });
2
  • 1
    Thank you for pointing that out Joe, you are correct, I have corrected the desired result Commented Dec 11, 2018 at 4:11
  • If you are able to format your dates as YYYY-MM-DD, they will sort lexically and so be easier to deal with. Commented Dec 11, 2018 at 6:16

2 Answers 2

2

You can map() over the dates since you want one result array for each date. Then within the map you can filter() the people based on the date to create that array:

let dates = ['2017-11-5', '2018-3-1', '2018-3-22']

let people = [{name: 'Jes', date: '2017-11-2'}, {name: 'Jill', date: '2018-1-5'}, {name: 'Joe', date: '2018-2-25'}, {name: 'Jack', date: '2018-3-21'}]

let cumul = dates.map(d => people.filter(person => person.date <= d))
console.log(JSON.stringify(cumul, null, 2))

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks mark! This creates the cumulative arrays i was looking for, but it only returns the date values, how can include the name property as well?
@user2821694 I'm seeing name when I run this snippet. You're not?
This will not work reliably as given person.date <= d, 2018-11-15 will sort as less than 2018-11-2. If the dates are changed to ISO 8601 format, then it will sort correctly.
You're right @RobG. It would be helpful if the dates were ISO 8601 (i.e. 2018-11-02)
@Mark Meyer, this solution did work for me however it did not improve the performance, it take 50-60 seconds for this to run when I use my real data set
0

There are several ways you can improve your algorithm while keeping the associative indices in enter image description here:

  • Precalculate date objects and only calculate them once for both people and dates
  • Sort dates in an ascending manner
  • Re-use precalculated/pre-filtered results, see Dynamic Programming
  • Save dates and people indices to keep the original order
  • Swap items between remaining and lastChunk to reduce remaining size if people[i].date < dates[j]

The algorithm code:

function filter(dates, people){
    let lastChunk = [];
    let remaining = people;
    let results = [];
    // precalculate dates, preserve indexes and sort by date
    let sortedDates = dates
        .map((value, index) => {
            return {
                date: new Date(value),
                index: index
            };
        })
        .sort((a, b) => {
            return a.date<b.date?-1:a.date==b.date?0:1;
        });
    let peopleWithDates = people.map((value, index) => {
        value.dateObject = new Date(value.date);
        value.index = index;
        return value;
    });
    for(const i in sortedDates){
        const comp = sortedDates[i].date
        remaining = remaining.filter((value, index) => {
            if(value.dateObject<=comp){
                let itemIndex = value.index;
                delete value.dateObject;
                delete value.index;
                lastChunk.splice(itemIndex, 0, value);
                return false;
            }else{
                return true;
            }
        });
        results[sortedDates[i].index] = [...lastChunk];
    }
    return results;
}

8 Comments

new Date(value) will result in an invalid date in Safari at least as the timestamps are not compliant with any of the formats specified in ECMA-262.
This could be solved like this: const parseDate = date => new Date(date+"T00:00:00"); which result in parsing date in UTC format.
No, it can't. The problem is that "2018-3-1" is not consistent with ECMA-262 (or ISO 8601). If it was compliant, i.e. "2018-03-01", it should be parsed as UTC anyway. But that's irrelevant for the sake of sorting.
Yeah, my bad, missing the left padding.
This could be solved this way: const parseDate = date => { const [year, month, day] = date.split("-"); return new Date(parseInt(year), parseInt(month)-1, parseInt(day)); }
|

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.