Search code examples
javascriptmedian

Finding exact number of hours, minutes, seconds from median of array of total number of seconds


Part of an overall challenge, I need my code to find the median of an array of numbers (with even or odd number of elements) representing total seconds and convert that number into an array containing the hours, minutes, and seconds from the total seconds. The code works well but it is not passing tests, especially random tests. In each case, the total seconds is turning out to be one less or one more than the test shows.

How can I solve this with the current code, or should I do something totally different?

I've tried manipulating the code with different Math methods, but I still get the problem of seconds being one higher or lower than the correct answer.

Here is my code so far:

concatInt = [ 3432, 4331, 7588, 8432 ]

let rawMedian = function (){

    if(concatInt.length === 0) return 0;

    concatInt.sort(function(a,b){
    return a-b;
    });

    var half = Math.floor(concatInt.length / 2);

    if (concatInt.length % 2)
    return concatInt[half];

    return (concatInt[half - 1] + concatInt[half]) / 2.0;
}

let toStopwatch = function(){
    let hours;
    let minutes;
    let seconds;

    if (rawMedian()/3600 > 1){
    hours = Math.floor(rawMedian()/3600);
    minutes = ((rawMedian() % 3600)/60);
    seconds = Math.floor((minutes - Math.floor(minutes)) * 60);

        if (seconds === 60){
            seconds = seconds -1;
        }

    } else {
    hours = 0;

    if (rawMedian() > 60){
        minutes = (rawMedian()/60);
        seconds = Math.floor((minutes - Math.floor(minutes)) * 60);

        if (seconds === 60){
            seconds = seconds -1;
        }

    } else {
        minutes = 0;
        seconds = rawMedian()
    }
    }

    let timesArr = [];

    timesArr.push(`${hours.toString()}`, `${Math.floor(minutes).toString()}`, `${seconds.toString()}`);
    return timesArr;
}

The result of this code is ["1", "39", "19"]. However, in Codewars Kata tests, the above code is showing to be incorrect because the number of seconds is one more or less than the expected number of seconds. I'm happy to provide my full code and the specific Kata involved upon request, but the code above is obviously causing the problem.


Solution

  • There are several issues, but the first two could explain why you get a difference of one second:

    • When the concatInt array has an even number of values, the median will have a half second. You did not say what should happen with that half second, but you seem to truncate it. It might well be that the requirement is to round it.

    • The way you calculate the seconds is prone to floating point imprecision errors:

      Math.floor((minutes - Math.floor(minutes)) * 60)
      

      For instance, if minutes is 8.6 then this expression -- before applying Math.floor -- will result in: 35.99999999999998 instead of 36. You should avoid multiplying with 60 there. Instead you could just get the number of seconds like this:

      median % 60
      
    • You call rawMedian repeatedly, which is a waste of time. Just call it once and store the result in a variable for further use.

    • It can never happen that the if (seconds === 60) condition is true. That code can be removed.
    • The other if constructs you have used are also unnecessary. Whether or not the median is greater than 1 is not relevant for the calculation.
    • It seems strange that the hours, minutes and seconds should be returned as strings instead of numbers. If this really is the case, then using template literals and calling the toString() method is overkill. One of both strategies is enough.

    So, assuming the median should be rounded, here is how the code could look:

    const toStopwatch = function() {
        // Call rawMedian only once.
        // Round it (or if it needs to be truncated, use Math.floor).
        const median = Math.round(rawMedian());
        // Calculation of seconds should just be a modulo 60.
        // No special cases need to be distinguished.
        // Do not cast to strings, unless explicitly required
        return [
            Math.floor(median / 3600),        // hours
            Math.floor((median % 3600) / 60), // minutes
            median % 60                       // seconds
        ];
    }