Find the fastest run of each year from a list of running results Problem Explained:

Imagine there is a list of running results of 1000m, like following:

[
    { id: 1, date: '2017-01-01 00:00:00', duration: 195 },
    { id: 2, date: '2017-01-10 00:00:00', duration: 270 },
    { id: 3, date: '2017-03-12 00:00:00', duration: 220 },
    { id: 4, date: '2018-01-10 00:00:00', duration: 218 },
    { id: 5, date: '2018-02-23 00:00:00', duration: 220 },
    { id: 6, date: '2018-05-18 00:00:00', duration: 215 }
]


I'd like to find out the fastest run of each year, which will be

[
    { id: 1, date: '2017-01-01 00:00:00', duration: 195 },
    { id: 6, date: '2018-05-18 00:00:00', duration: 215 }
]


Here's my ugly solution (TypeScript):

const items = JSON.parse('Use the JSON above.');
const results: any[] = [];
const fastestOfEachYear: object = {};
const idsOfFastestEachYear: object = {};

// Go through the list to find the fastest of each year.
// Save the IDs in an object.
items.forEach((item) =&gt; {
    const runId = item['id'];
    const date = item['date'];
    const dateParts = date.split('-');
    const year = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]).getFullYear();
    const duration = item['duration'];

    // Found a faster one, update our objects with the new item.
    if (fastestOfEachYear[year] == null || fastestOfEachYear[year] &gt; duration) {
        fastestOfEachYear[year] = duration;
        idsOfFastestEachYear[year] = runId;
    }
});

// Retrieve the IDs of the runs that are the fastest of a year.
const idsOfFastest = Object.keys(idsOfFastestEachYear).map((key) =&gt; idsOfFastestEachYear[key]);

// Loop through the list again to find those items matching the IDs
// Save them to results.
items.forEach((item) =&gt; {
    const runId = item['id'];
    if ($.inArray(runId, idsOfFastest) !== -1) {
        results.push(item);
    }
});


Surely there are nicer and better ways of achieving this? (Reviews in TypeScript, ES6 or even just Vanilla JS are ok.)
First solution using .forEach, for...in and .sort

Here what I did to achieve this:


First sort your array of objects, i.e convert:

data = [ {...}, {...}, {...}, {...}, {...}, {...} ]


into an object sorted by year:

sortedY = { 2017: [{...}, {...}, {...}], 2018: [{...}, {...}, {...}] }


You can achieve such result by using a forEach loop:

const sortedY = {};
data.forEach(e =&gt; {
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] || [];
    sortedY[year].push(e);
});

Then we will need to sort each object [{...}, {...}, {...}] per year by duration. sorted is not an array. We can't use map unfortunately: use a for...in loop instead. However to sort each key/value (&lt;=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:

(a,b) =&gt; a.duration &gt; b.duration


Where a and b are two different races from the same year.

So with:

sortedY[year].sort((a,b) =&gt; a.duration &gt; b.duration)


You get an array of sorted races for the year year.

The last thing to do is saving the first of the sorted array (the first is the one with the shortest duration, so select it with [0] and add it to result:

result[year] = sortedY[year].sort((a,b) =&gt; a.duration &gt; b.duration)[0]





The final code is:



const sortedY = {};
data.forEach(e =&gt; {
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] || [];
    sortedY[year].push(e);
});

const result = {};
for(year in sortedY) {
   result[year] = sortedY[year].sort((a,b) =&gt; a.duration &gt; b.duration)[0];
}

console.log(result);
&lt;script&gt;
const data = [ { id: 1, date: '2017-01-01 00:00:00', duration: 195 }, { id: 2, date: '2017-01-10 00:00:00', duration: 270 }, { id: 3, date: '2017-03-12 00:00:00', duration: 220 }, { id: 4, date: '2018-01-10 00:00:00', duration: 218 }, { id: 5, date: '2018-02-23 00:00:00', duration: 220 }, { id: 6, date: '2018-05-18 00:00:00', duration: 215 } ]
&lt;/script&gt;






Second solution using only .forEach

I realised there was a quicker way of doing this: we don't need to loop over the array data twice! One loop will suffice. Indeed, we'll fill result as we navigate through data only replacing a yearly race if a race is shorter in duration.

So a race will be added to result[year] only if:

!result[year] || (result[year] &amp;&amp; e.duration &lt; result[year].duration)


is true, i.e. if either:


there is no race registered for the year year
there is one but its duration is longer than the one that we're checking


So the code is shorter, simpler and I bet it will be quicker: its complexity is \$O(n)\$. The previous code was cool but not so much efficient. I was looping twice the data: first time for the sorting, second on the sorted array. Not even mentioning that I was using sort...



let result = {};
data.forEach(e =&gt; {
  const year = e.date.split('-')[0];
  if( !result[year] || (result[year] &amp;&amp; e.duration &lt; result[year].duration) ) {
    result[year] = e;
  }
})

console.log(result);
&lt;script&gt;
const data = [ { id: 1, date: '2017-01-01 00:00:00', duration: 195 }, { id: 2, date: '2017-01-10 00:00:00', duration: 270 }, { id: 3, date: '2017-03-12 00:00:00', duration: 220 }, { id: 4, date: '2018-01-10 00:00:00', duration: 218 }, { id: 5, date: '2018-02-23 00:00:00', duration: 220 }, { id: 6, date: '2018-05-18 00:00:00', duration: 215 } ]
&lt;/script&gt;