0
\$\begingroup\$

I'm just wondering Is there any ways I could simplify the following code without making it too difficult:

Actually I want to extract specific year data from content node based on lastNumberOfYears variable.

Example: Content array has 2019,2018,2017,2016 years data and lastNumberOfYears value is 2 then output should have only 2019, 2018 data. other scenario is if content array is like 2020, 2014, 2013 then output should have 2020, 2014 data (if lastNumberOfYears value is 2)

My solution

Step 1 : Define predefined data

const content = [{
    "document": "/content/path/document1.pdf",
    "date": "2020-02-20"
  }, {
    "document": "/content/path/file.docx",
    "date": "2019-02-20"
  }, {
    "document": "/content/abc/document2.docx",
    "date": "2018-06-20"
  }, {
    "document": "/document/order/order.pdf",
    "date": "2020-06-20"
  }, {
    "document": "/content/order/update.pdf",
    "date": "2018-03-21"
  }, {
    "document": "/content/author/author.pdf",
    "date": "2017-03-21"
  }, {
    "document": "/content/path/dummy.pdf",
    "date": "2016-02-15"
  }, {
    "document": "/content/dummy/dummy.docx",
    "date": "2015-12-15"
}];

let lastNumberOfYears = 2;
let selectedYearArray = [];

Step 2: Convert content data into year base data

const result = content.reduce((current, value) => {
  const dateYear = new Date(value.date).getFullYear();
  current[dateYear] = current[dateYear] || [];
  current[dateYear].push(value);
  return current;
}, {});

/*
output is like
{
   "2015":[{...}],"2016":[{}],..."2020":[{}, {}]
}
*/

Step 3: Put specific year data in same order for result array

Object.values(result).forEach(value => value.reverse());

Step 4: Create Year array from object key

const yearsArray = Object.keys(result).sort((first, second) => (first > second ? -1 : 1));

Step 5: Create last number of year array using lastNumberOfYears variable Step 6: Extract Step 5 data from result array define in step 3 Step 7: Remove year key and join all array value of Step 6 outcome

if (yearsArray.length !== 0 && lastNumberOfYears <= yearsArray.length) {
  for (let i = 0; i < lastNumberOfYears; i++) {
    selectedYearArray.push(yearsArray[i]);
  }

  /*
    Step 5: console.log(selectedYearArray);
  */

  const filteredResult = Object.keys(result)
    .filter(key => selectedYearArray.includes(key))
    .reduce((obj, key) => {
      obj[key] = result[key];
      return obj;
    }, {});

  /*
    Step 6: console.log(filteredResult);
  */

  contentObj = [].concat.apply([],Object.values(filteredResult));

  /*
    Step 7:
  */
  console.log(contentObj.reverse());
}

Output:

[{
    "document": "/content/path/document1.pdf",
    "date": "2020-02-20"
}, {
    "document": "/document/order/order.pdf",
    "date": "2020-06-20"
}, {
    "document": "/content/path/file.docx",
    "date": "2019-02-20"
}]
\$\endgroup\$
7
  • \$\begingroup\$ This is working code but time complexity is bit high so want to know how to improve time complexity. \$\endgroup\$ Feb 28, 2020 at 19:15
  • \$\begingroup\$ What is your criteria for improvement? Just execution time for a given set of data? My first thought would be use a Map object with year as the index and retain that Map so you can easily look up any items by year from then on. \$\endgroup\$
    – jfriend00
    Feb 28, 2020 at 19:19
  • \$\begingroup\$ Actually I want to do it in single step like single filter function involve not too much steps like here total 6-7 steps involve. \$\endgroup\$ Feb 28, 2020 at 19:27
  • 1
    \$\begingroup\$ I can't really tell what your criteria is for evaluating what is "better"? You mention reducing steps? You mention time complexity? You mention simplifying? I'd say you need to state an objective criteria and focus the post on that specific criteria. \$\endgroup\$
    – jfriend00
    Feb 28, 2020 at 19:31
  • \$\begingroup\$ my criteria is time complexity and performance. \$\endgroup\$ Feb 28, 2020 at 19:32

1 Answer 1

3
\$\begingroup\$

Here's a scheme that appears to be about 7 times faster that yours when run in node.js:

function getData2(content, lastNumberOfYears) {
    // organize data by year into a Map object
    const map = new Map()
    for (let item of content) {
        const year = new Date(item.date).getFullYear();
        let yearData = map.get(year);
        if (yearData) {
            yearData.push(item);
        } else {
            map.set(year, [item]);
        }
    }

    /* map object data looks like this:
        Map {
          2020 => [
            { document: '/content/path/document1.pdf', date: '2020-02-20' },
            { document: '/document/order/order.pdf', date: '2020-06-20' }
          ],
          2019 => [ { document: '/content/path/file.docx', date: '2019-02-20' } ],
          2018 => [
            { document: '/content/abc/document2.docx', date: '2018-06-20' },
            { document: '/content/order/update.pdf', date: '2018-03-21' }
          ],
          2017 => [ { document: '/content/author/author.pdf', date: '2017-03-21' } ],
          2016 => [ { document: '/content/path/dummy.pdf', date: '2016-02-15' } ],
          2015 => [ { document: '/content/dummy/dummy.docx', date: '2015-12-15' } ]
        }
    */

    // get lastNumberOfYears by getting all the years, sorting them 
    // and getting most recent ones
    let sortedYears = Array.from(map.keys()).sort();
    if (sortedYears.length > lastNumberOfYears) {
        sortedYears = sortedYears.slice(-lastNumberOfYears);
    }
    let result = [];
    for (let year of sortedYears) {
        result.push(map.get(year));
    }
    return result.flat();

}

function time(fn) {
    const start = process.hrtime.bigint();
    let result = fn();
    const end = process.hrtime.bigint();
    console.log(result);
    console.log(`Benchmark took ${end - start} nanoseconds`);
}

It uses the following steps:

  1. Parse years and put each year's into a Map object with the year as the key and the value as an array of values for that year
  2. Get the years from the Map object, sort them and get the desired lastNumberOfYears
  3. Collect the results from the Map object for each of the chosen years and flatten them into a single array

If you want to run both yours and mine in node.js, here's the entire program I used to compare:

const inputData = [{
    "document": "/content/path/document1.pdf",
    "date": "2020-02-20"
  }, {
    "document": "/content/path/file.docx",
    "date": "2019-02-20"
  }, {
    "document": "/content/abc/document2.docx",
    "date": "2018-06-20"
  }, {
    "document": "/document/order/order.pdf",
    "date": "2020-06-20"
  }, {
    "document": "/content/order/update.pdf",
    "date": "2018-03-21"
  }, {
    "document": "/content/author/author.pdf",
    "date": "2017-03-21"
  }, {
    "document": "/content/path/dummy.pdf",
    "date": "2016-02-15"
  }, {
    "document": "/content/dummy/dummy.docx",
    "date": "2015-12-15"
}];

function getData1(content, lastNumberOfYears) {

    let selectedYearArray = [];

    const result = content.reduce((current, value) => {
      const dateYear = new Date(value.date).getFullYear();
      current[dateYear] = current[dateYear] || [];
      current[dateYear].push(value);
      return current;
    }, {});

    /*
    output is like
    {
       "2015":[{...}],"2016":[{}],..."2020":[{}, {}]
    }
    */

    Object.values(result).forEach(value => value.reverse());

    const yearsArray = Object.keys(result).sort((first, second) => (first > second ? -1 : 1));

    if (yearsArray.length !== 0 && lastNumberOfYears <= yearsArray.length) {
      for (let i = 0; i < lastNumberOfYears; i++) {
        selectedYearArray.push(yearsArray[i]);
      }

      /*
        Step 5: console.log(selectedYearArray);
      */

      const filteredResult = Object.keys(result)
        .filter(key => selectedYearArray.includes(key))
        .reduce((obj, key) => {
          obj[key] = result[key];
          return obj;
        }, {});

      /*
        Step 6: console.log(filteredResult);
      */

      contentObj = [].concat.apply([],Object.values(filteredResult));

      /*
        Step 7:
      */
      return contentObj.reverse();
    }
}

function getData2(content, lastNumberOfYears) {
    // organize data by year into a Map object
    const map = new Map()
    for (let item of content) {
        const year = new Date(item.date).getFullYear();
        let yearData = map.get(year);
        if (yearData) {
            yearData.push(item);
        } else {
            map.set(year, [item]);
        }
    }

    // get lastNumberOfYears by getting all the years, sorting them 
    // and getting most recent ones
    let sortedYears = Array.from(map.keys()).sort();
    if (sortedYears.length > lastNumberOfYears) {
        sortedYears = sortedYears.slice(-lastNumberOfYears);
    }
    let result = [];
    for (let year of sortedYears) {
        result.push(map.get(year));
    }
    return result.flat();

}

function time(fn) {
    const start = process.hrtime.bigint();
    let result = fn();
    const end = process.hrtime.bigint();
    console.log(result);
    console.log(`Benchmark took ${end - start} nanoseconds`);
}

time(() => getData1(inputData, 2));

time(() => getData2(inputData, 2));

And, the output on my desktop computer was this:

[
  { document: '/content/path/document1.pdf', date: '2020-02-20' },
  { document: '/document/order/order.pdf', date: '2020-06-20' },
  { document: '/content/path/file.docx', date: '2019-02-20' }
]
Benchmark took 602501 nanoseconds
[
  { document: '/content/path/file.docx', date: '2019-02-20' },
  { document: '/content/path/document1.pdf', date: '2020-02-20' },
  { document: '/document/order/order.pdf', date: '2020-06-20' }
]
Benchmark took 89100 nanoseconds

Note a slightly different sort order of the results. I didn't see in your description if a specific sort order of results was required or not.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks man, Yes its too fast. \$\endgroup\$ Feb 28, 2020 at 20:13
  • \$\begingroup\$ Map and sortedYears.slice(-lastNumberOfYears); is killer. \$\endgroup\$ Feb 28, 2020 at 20:32

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.