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

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite
1












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) => );

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

// Loop through the list again to find those items matching the IDs
// Save them to results.
items.forEach((item) =>
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.)







share|improve this question





















  • Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
    – tokland
    Jun 10 at 18:35










  • @tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
    – Yi Zeng
    Jun 11 at 2:37
















up vote
7
down vote

favorite
1












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) => );

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

// Loop through the list again to find those items matching the IDs
// Save them to results.
items.forEach((item) =>
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.)







share|improve this question





















  • Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
    – tokland
    Jun 10 at 18:35










  • @tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
    – Yi Zeng
    Jun 11 at 2:37












up vote
7
down vote

favorite
1









up vote
7
down vote

favorite
1






1





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) => );

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

// Loop through the list again to find those items matching the IDs
// Save them to results.
items.forEach((item) =>
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.)







share|improve this question













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) => );

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

// Loop through the list again to find those items matching the IDs
// Save them to results.
items.forEach((item) =>
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.)









share|improve this question












share|improve this question




share|improve this question








edited Jun 15 at 16:46









200_success

123k14143399




123k14143399









asked Jun 10 at 9:26









Yi Zeng

1415




1415











  • Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
    – tokland
    Jun 10 at 18:35










  • @tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
    – Yi Zeng
    Jun 11 at 2:37
















  • Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
    – tokland
    Jun 10 at 18:35










  • @tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
    – Yi Zeng
    Jun 11 at 2:37















Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
– tokland
Jun 10 at 18:35




Are you interested in solutions using lodash/underscore or similar? It's so much easier with good abstractions at hand.
– tokland
Jun 10 at 18:35












@tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
– Yi Zeng
Jun 11 at 2:37




@tokland: Yes, I thought about using some handy methods from lodash, but I struggled to introduce lodash into my Rails + webpacker + TypeScript project. But yes, a lodash solution would be acceptable too!
– Yi Zeng
Jun 11 at 2:37










3 Answers
3






active

oldest

votes

















up vote
6
down vote



accepted










First solution using .forEach, for...in and .sort



Here what I did to achieve this:




  1. 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 =>
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] );



  2. 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 (<=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:



    (a,b) => a.duration > b.duration


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



    So with:



    sortedY[year].sort((a,b) => a.duration > 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) => a.duration > b.duration)[0]



The final code is:






const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>






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] && e.duration < 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 =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>








share|improve this answer



















  • 2




    Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
    – Yi Zeng
    Jun 11 at 7:26

















up vote
3
down vote













@Ivan has said enough already, so I'll allow myself to include only code that will give you exactly the same output that you expected in your question.



It works just like @Ivan's second solution, except that it uses .reduce() and gives you only values of the same resultant object as his, which is what you were looking for. $O(n)$ complexity.






const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);








share|improve this answer

















  • 1




    Nice elegant solution. Thanks!
    – Yi Zeng
    Jun 11 at 7:27






  • 1




    Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
    – Ivan
    Jun 11 at 12:28

















up vote
1
down vote













The problem is that you are kind of re-implenting abstractions (groupBy, min, ...) found in utility libraries like lodash, underscore, ramda... So my advice would be to just use one of these and write declarative/functional code. Something like this:



const _ = require('lodash');
const getYear = run => parseInt(run.date.split("-")[0]);
const fastestRunByYear = _(runs)
.groupBy(getYear)
.map(runsForYear => _.minBy(runsForYear, "duration"))
.value();


If not using external libraries was a requirement, for whatever reason, then I'd implement groupBy and minBy as a separate functions and use the same code above (without _ wrappings, of course). Note that I could move the getYearcode into the map, but I create a separate function to make it more clear.






share|improve this answer























  • Thanks! Yes, now I have Lodash in my project.
    – Yi Zeng
    Jun 14 at 1:19











Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196223%2ffind-the-fastest-run-of-each-year-from-a-list-of-running-results%23new-answer', 'question_page');

);

Post as a guest






























3 Answers
3






active

oldest

votes








3 Answers
3






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
6
down vote



accepted










First solution using .forEach, for...in and .sort



Here what I did to achieve this:




  1. 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 =>
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] );



  2. 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 (<=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:



    (a,b) => a.duration > b.duration


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



    So with:



    sortedY[year].sort((a,b) => a.duration > 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) => a.duration > b.duration)[0]



The final code is:






const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>






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] && e.duration < 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 =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>








share|improve this answer



















  • 2




    Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
    – Yi Zeng
    Jun 11 at 7:26














up vote
6
down vote



accepted










First solution using .forEach, for...in and .sort



Here what I did to achieve this:




  1. 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 =>
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] );



  2. 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 (<=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:



    (a,b) => a.duration > b.duration


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



    So with:



    sortedY[year].sort((a,b) => a.duration > 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) => a.duration > b.duration)[0]



The final code is:






const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>






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] && e.duration < 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 =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>








share|improve this answer



















  • 2




    Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
    – Yi Zeng
    Jun 11 at 7:26












up vote
6
down vote



accepted







up vote
6
down vote



accepted






First solution using .forEach, for...in and .sort



Here what I did to achieve this:




  1. 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 =>
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] );



  2. 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 (<=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:



    (a,b) => a.duration > b.duration


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



    So with:



    sortedY[year].sort((a,b) => a.duration > 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) => a.duration > b.duration)[0]



The final code is:






const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>






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] && e.duration < 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 =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>








share|improve this answer















First solution using .forEach, for...in and .sort



Here what I did to achieve this:




  1. 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 =>
    const year = e.date.split('-')[0];
    sortedY[year] = sortedY[year] );



  2. 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 (<=> year/array of results for this year) we can use one of Array's methods: sort, the sorting criteria is the following:



    (a,b) => a.duration > b.duration


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



    So with:



    sortedY[year].sort((a,b) => a.duration > 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) => a.duration > b.duration)[0]



The final code is:






const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>






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] && e.duration < 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 =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>








const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>





const sortedY = ;
data.forEach(e =>
const year = e.date.split('-')[0];
sortedY[year] = sortedY[year] );

const result = ;
for(year in sortedY)
result[year] = sortedY[year].sort((a,b) => a.duration > b.duration)[0];


console.log(result);

<script>
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 ]
</script>





let result = ;
data.forEach(e =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>





let result = ;
data.forEach(e =>
const year = e.date.split('-')[0];
if( !result[year] )

console.log(result);

<script>
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 ]
</script>






share|improve this answer















share|improve this answer



share|improve this answer








edited Jun 15 at 14:49


























answered Jun 10 at 13:05









Ivan

33511




33511







  • 2




    Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
    – Yi Zeng
    Jun 11 at 7:26












  • 2




    Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
    – Yi Zeng
    Jun 11 at 7:26







2




2




Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
– Yi Zeng
Jun 11 at 7:26




Very elegant solution. Thanks! I'll accept once I've got a chance to try it out.
– Yi Zeng
Jun 11 at 7:26












up vote
3
down vote













@Ivan has said enough already, so I'll allow myself to include only code that will give you exactly the same output that you expected in your question.



It works just like @Ivan's second solution, except that it uses .reduce() and gives you only values of the same resultant object as his, which is what you were looking for. $O(n)$ complexity.






const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);








share|improve this answer

















  • 1




    Nice elegant solution. Thanks!
    – Yi Zeng
    Jun 11 at 7:27






  • 1




    Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
    – Ivan
    Jun 11 at 12:28














up vote
3
down vote













@Ivan has said enough already, so I'll allow myself to include only code that will give you exactly the same output that you expected in your question.



It works just like @Ivan's second solution, except that it uses .reduce() and gives you only values of the same resultant object as his, which is what you were looking for. $O(n)$ complexity.






const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);








share|improve this answer

















  • 1




    Nice elegant solution. Thanks!
    – Yi Zeng
    Jun 11 at 7:27






  • 1




    Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
    – Ivan
    Jun 11 at 12:28












up vote
3
down vote










up vote
3
down vote









@Ivan has said enough already, so I'll allow myself to include only code that will give you exactly the same output that you expected in your question.



It works just like @Ivan's second solution, except that it uses .reduce() and gives you only values of the same resultant object as his, which is what you were looking for. $O(n)$ complexity.






const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);








share|improve this answer













@Ivan has said enough already, so I'll allow myself to include only code that will give you exactly the same output that you expected in your question.



It works just like @Ivan's second solution, except that it uses .reduce() and gives you only values of the same resultant object as his, which is what you were looking for. $O(n)$ complexity.






const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);








const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);





const getFastestRuns = input => Object.values(input.reduce((acc, curr) => , ));

/* DEMO */

const input = [
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
];

console.log(
JSON.stringify(
getFastestRuns(input)
)
);






share|improve this answer













share|improve this answer



share|improve this answer











answered Jun 10 at 22:06









Przemek

1,032213




1,032213







  • 1




    Nice elegant solution. Thanks!
    – Yi Zeng
    Jun 11 at 7:27






  • 1




    Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
    – Ivan
    Jun 11 at 12:28












  • 1




    Nice elegant solution. Thanks!
    – Yi Zeng
    Jun 11 at 7:27






  • 1




    Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
    – Ivan
    Jun 11 at 12:28







1




1




Nice elegant solution. Thanks!
– Yi Zeng
Jun 11 at 7:27




Nice elegant solution. Thanks!
– Yi Zeng
Jun 11 at 7:27




1




1




Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
– Ivan
Jun 11 at 12:28




Nice, I tried using only .reduce in my second attempt but failed. I didn't know the method could take initialValue as [the second argument]. Thanks for sharing +1.
– Ivan
Jun 11 at 12:28










up vote
1
down vote













The problem is that you are kind of re-implenting abstractions (groupBy, min, ...) found in utility libraries like lodash, underscore, ramda... So my advice would be to just use one of these and write declarative/functional code. Something like this:



const _ = require('lodash');
const getYear = run => parseInt(run.date.split("-")[0]);
const fastestRunByYear = _(runs)
.groupBy(getYear)
.map(runsForYear => _.minBy(runsForYear, "duration"))
.value();


If not using external libraries was a requirement, for whatever reason, then I'd implement groupBy and minBy as a separate functions and use the same code above (without _ wrappings, of course). Note that I could move the getYearcode into the map, but I create a separate function to make it more clear.






share|improve this answer























  • Thanks! Yes, now I have Lodash in my project.
    – Yi Zeng
    Jun 14 at 1:19















up vote
1
down vote













The problem is that you are kind of re-implenting abstractions (groupBy, min, ...) found in utility libraries like lodash, underscore, ramda... So my advice would be to just use one of these and write declarative/functional code. Something like this:



const _ = require('lodash');
const getYear = run => parseInt(run.date.split("-")[0]);
const fastestRunByYear = _(runs)
.groupBy(getYear)
.map(runsForYear => _.minBy(runsForYear, "duration"))
.value();


If not using external libraries was a requirement, for whatever reason, then I'd implement groupBy and minBy as a separate functions and use the same code above (without _ wrappings, of course). Note that I could move the getYearcode into the map, but I create a separate function to make it more clear.






share|improve this answer























  • Thanks! Yes, now I have Lodash in my project.
    – Yi Zeng
    Jun 14 at 1:19













up vote
1
down vote










up vote
1
down vote









The problem is that you are kind of re-implenting abstractions (groupBy, min, ...) found in utility libraries like lodash, underscore, ramda... So my advice would be to just use one of these and write declarative/functional code. Something like this:



const _ = require('lodash');
const getYear = run => parseInt(run.date.split("-")[0]);
const fastestRunByYear = _(runs)
.groupBy(getYear)
.map(runsForYear => _.minBy(runsForYear, "duration"))
.value();


If not using external libraries was a requirement, for whatever reason, then I'd implement groupBy and minBy as a separate functions and use the same code above (without _ wrappings, of course). Note that I could move the getYearcode into the map, but I create a separate function to make it more clear.






share|improve this answer















The problem is that you are kind of re-implenting abstractions (groupBy, min, ...) found in utility libraries like lodash, underscore, ramda... So my advice would be to just use one of these and write declarative/functional code. Something like this:



const _ = require('lodash');
const getYear = run => parseInt(run.date.split("-")[0]);
const fastestRunByYear = _(runs)
.groupBy(getYear)
.map(runsForYear => _.minBy(runsForYear, "duration"))
.value();


If not using external libraries was a requirement, for whatever reason, then I'd implement groupBy and minBy as a separate functions and use the same code above (without _ wrappings, of course). Note that I could move the getYearcode into the map, but I create a separate function to make it more clear.







share|improve this answer















share|improve this answer



share|improve this answer








edited Jun 11 at 8:31


























answered Jun 11 at 8:04









tokland

10.7k11322




10.7k11322











  • Thanks! Yes, now I have Lodash in my project.
    – Yi Zeng
    Jun 14 at 1:19

















  • Thanks! Yes, now I have Lodash in my project.
    – Yi Zeng
    Jun 14 at 1:19
















Thanks! Yes, now I have Lodash in my project.
– Yi Zeng
Jun 14 at 1:19





Thanks! Yes, now I have Lodash in my project.
– Yi Zeng
Jun 14 at 1:19













 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196223%2ffind-the-fastest-run-of-each-year-from-a-list-of-running-results%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Python Lists

Aion

JavaScript Array Iteration Methods