Find maximum possible time HH:MM by permuting four given digits

前端 未结 23 2056
执念已碎
执念已碎 2020-11-30 02:44

I recently took a coding test for a promotion at work. This was one of the tasks I really struggled with and was wondering what is the best way to do this. I used a load of

相关标签:
23条回答
  • 2020-11-30 02:56

    Here's my attempt. Added inline comments with explanations.

    // think of the result of the form {h1}{h2}:{ms}
    function generate(a, b, c, d) {
      const digits = [a, b, c, d];
    
      // extract possible starting digits
      const possibleH1s = [2, 1, 0].filter(digit => digits.includes(digit));
      
      // check result, starting from the highest possible h1 digit
      // if digits doesn't contains any of [2,1,0], we're done
      for (const h1 of possibleH1s) {
    
        // extract the remaining digits after h1
        const withoutH1 = removeFrom(digits, h1);
        
        // determine all possible h2 digits based on the h1 digit
        const possibleH2s = h1 === 2
          ? [3,2,1,0]
          : [9,8,7,6,5,4,3,2,1,0];
    
        // find the highest possible h2 digit (works because array of possible digits above is in descending order)
        // if none exist, loop iteration is done
        const h2 = possibleH2s.find(d => withoutH1.includes(d));
        if (typeof h2 !== 'number') {
          continue;
        }
        
        // remove h2 so we can search for the remaining ms digits
        const [possibleMS1, possibleMS2] = removeFrom(withoutH1, h2);
        
        // build the two possible combinations for ms    
        const possibleMs = [
          Number(`${possibleMS1}${possibleMS2}`),
          Number(`${possibleMS2}${possibleMS1}`)
        ];
        
        // determine the min and max ms value
        const maxMs = Math.max(...possibleMs);
        const minMs = Math.min(...possibleMs);
    
        // find the largest valid ms value
        // if none exist, loop iteration is done
        const ms = maxMs < 60 ? maxMs : minMs < 60 ? minMs : undefined;
        if (typeof ms !== 'number') {
          continue;
        }
    
        // yay, time
        return `${h1}${h2}:${padWithZero(ms)}`;
      }
      
      return 'NOT POSSIBLE';
    }
    
    // returns a new array by removing a single element 
    // that is equal to `val` from the given array
    // (performs better than splice cause if doesn't do array shift)
    function removeFrom(arr, val) {
      const newArr = [];
      for (let i = 0, l = arr.length, found = false; i < l; i++) {
        if (arr[i] !== val || found) {
          newArr.push(arr[i]);
        } else {
          found = true;
        }
      }
      return newArr;
    }
    
    function padWithZero(digit) {
      return digit < 10 ? `0${digit}` : `${digit}`;
    }
    
    /* --------------- Tests --------------------- */
    
    const speedTest = (times = 10000) => {
      let counter = times;
      const start = performance.now();
      while (counter--) {
        const A = randNum();
        const B = randNum();
        const C = randNum();
        const D = randNum();
        generate(A, B, C, D);
        if (counter == 0) {
          const ms = performance.now() - start;
          console.log(`${times} times to run generate took ${ms} ms`);
        }
      }
    }
    
    const randNum = () => Math.floor(Math.random() * (9 - 0 + 1)) + 0;
    
    const accuracyTest = () => {
      console.assert(generate(1,7,2,7) === '17:27');
      console.assert(generate(0,0,2,9) === '20:09');
      console.assert(generate(6,5,2,0) === '20:56');
      console.assert(generate(3,9,5,0) === '09:53');
      console.assert(generate(7,6,3,8) === 'NOT POSSIBLE');
      console.assert(generate(0,0,0,0) === '00:00');
      console.assert(generate(9,9,9,9) === 'NOT POSSIBLE');
      console.assert(generate(1,2,3,4) === '23:41');
      console.log('All good!');
    }
    
    speedTest();
    accuracyTest();

    0 讨论(0)
  • 2020-11-30 02:56

    Really late for the party, but I think there a quite straightforward solution to the problem (slower and uglier than other solutions, though). Just iterate (no hardcoding, no permutations) through all integer values from 2359 to 0 and check if they contain provided digits:

    Number.prototype.pad = function(size) {
        var s = String(this);
        while (s.length < (size || 2)) {s = "0" + s;}
        return s;
    }
    
    getHHMM = (val) => `${Math.floor(val / 100).pad(2)}:${(val % 100).pad(2)}`;
    
    isValidDate = value => !isNaN(new Date(`1970-01-01T${getHHMM(value)}`).getTime());
    
    isFit = function(a, b, c, d, value) {
        var valStr = value.pad(4).split("").sort().join("");
        var digStr = [a, b, c, d].sort().join("");
        return valStr === digStr;
    }
    
    generate = function(a, b, c, d) {
        for (var i = 2359; i >= 0; i--) {
            if (isFit(a, b, c, d, i) && isValidDate(i))
                return getHHMM(i);
        }
        return "NOT POSSIBLE";
    }
    
    0 讨论(0)
  • 2020-11-30 02:58

    Reasoning about this got a lot easier once I clued in to the fact that you can treat the problem as "generate a number less than 24, and a number less than 60" instead of trying to work with individual digits.

    This goes through the number pairs in the set, finds the biggest valid hour that can be made from that pair of digits, then finds the biggest valid minute that can be made from the leftovers.

    var generate = function(a, b, c, d) {
      var biggest = function(a, b, max) {
        // returns largest of 'ab' or 'ba' which is below max, or false.
        // I'm sure there's a more concise way to do this, but:
        var x = '' + a + b;
        var y = '' + b + a;
        if (max > x && max > y) {
          var tmp = Math.max(x,y);
          return (tmp < 10) ? "0"+tmp : tmp;
        }
        if (max > x) return x;
        if (max > y) return y;
        return false;
      }
    
      var output = false;
    
      var input = [].slice.call(arguments);
      for (var i = 0; i < arguments.length; i++) {
        for (var j = i + 1; j < arguments.length; j++) {
          // for every pair of numbers in the input:
          var hour = biggest(input[i], input[j], 24); // What's the biggest valid hour we can make of that pair?
          if (hour) {
            // do the leftovers make a valid minute?
            var tmp = input.slice(); // copy the input
            tmp.splice(j, 1);
            tmp.splice(i, 1);
            var minute = biggest(tmp[0], tmp[1], 60);
            if (hour && minute) {
              // keep this one if it's bigger than what we had before:
              var nval = hour + ':' + minute;
              if (!output || nval > output) output = nval;
            }
          }
        }
      }
      return output || 'NOT POSSIBLE';
    }
    
    /* --------------- Start correctness test --------------------- */
      var tests = ['0000', '1212', '1234', '2359', '2360','2362','2366', '1415', '1112', '1277', '9999', '0101'];
    console.log('---');
    for (var i = 0; i < tests.length; i++) {
      console.log(
        tests[i],
        generate.apply(this, tests[i].split(''))
      )
    }
    
    
    
    /* --------------- Start Speed Test --------------------- */
    
    let startTime = Math.floor(Date.now());
    let times = 10000; //how many generate call you want?
    let timesHolder = times;
    
    while (times--) {
      let A = randNum();
      let B = randNum();
      let C = randNum();
      let D = randNum();
      generate(A, B, C, D);
      if (times == 0) {
        let totalTime = Math.floor(Date.now()) - startTime;
        let msg = timesHolder + ' Call Finished Within -> ' + totalTime + ' ms <-';
        console.log(msg);
        // alert(msg);
      }
    }
    
    function randNum() {
      return Math.floor(Math.random() * (9 - 0 + 1)) + 0;
    }
    
    /* --------------- END Speed Test --------------------- */

    0 讨论(0)
  • 2020-11-30 02:59

    The idea:

    • Find all combinations array (24 total)
    • filter out all invalid combinations (time format)
    • find the time value
    • output the array with the max time value

    Solution:

    First allCom will return all combination of the 4 number (total 24 combinations)

    Then for the 24 array (combinations) call .forEach go through each array check if it is a valid time format. If it is valid time format then calculate the time value with

    If the time is AB:CD then the value:

    A = A * 10 hours = A * 10 * 3600s = A * 36000s

    B = B * 1 hour = B * 3600s

    C = C * 10s

    D = D

    Total value = A*36000 + B*3600 + C*10 + D

    Now you got the value of the current array, compare with the saved Max, replace the max if this value is bigger.

    At the end of the loop determine if a max found or it is not valid.

    generate(6, 5, 2, 0);
    generate(3, 9, 5, 0);
    generate(7, 6, 3, 8);
    generate(1, 7, 2, 7);
    generate(1, 1, 1, 2);
    
    // return all combination of 4 number (24 combination total)
    function allCom(inputArray) {
      var result = inputArray.reduce(function permute(res, item, key, arr) {
        return res.concat(arr.length > 1 && arr.slice(0, key).concat(arr.slice(key + 1)).reduce(permute, []).map(function(perm) {
          return [item].concat(perm);
        }) || item);
      }, []);
      return result;
    }
    
    // core function to determine the max comb
    function generate(A, B, C, D) {
      let input = [A, B, C, D];
      let allComb = allCom(input);
      let max = '';
      let maxA = [];
    
      allComb.forEach(function(comb, index, arr) {
        if (validCom(comb)) {
          let temp = calValue(comb);
          maxA = temp > max ? comb : maxA;
          max = temp > max ? temp : max;
        }
        if (index == allComb.length - 1) {
          if (max) {
            return console.log('For ' + JSON.stringify(input) + ' found max comb: ' + maxA[0] + maxA[1] + ':' + maxA[2] + maxA[3]);
          }
          return console.log('Sorry ' + JSON.stringify(input) + ' is not valid');
        }
      });
    }
    
    // check if this array is valid time format, ex [1,2,9,0] false, [2,2,5,5] true
    function validCom(ar) {
      if (ar[0] <= 2 && ((ar[0] == 2 && ar[1] < 4) || (ar[0] != 2 && ar[1] <= 9)) && ar[2] <= 5 && ar[3] <= 9) {
        return true;
      }
      return false;
    }
    
    // calculate the total value of this comb array
    function calValue(ar) {
      return +ar[0] * 36000 + +ar[1] * 3600 + +ar[2] * 10 + +ar[0];
    }
    
    
    $('button').on('click', function(e) {
        let inp = $('select');
        generate(inp[0].value, inp[1].value, inp[2].value, inp[3].value);
    });
    
    
    var s = $('<select />');
    for(i=0;i<10;i++) {
        $('<option />', {value: i, text: i}).appendTo(s);
    }
    s.clone().appendTo('#myform');
    s.clone().appendTo('#myform');
    s.clone().appendTo('#myform');
    s.clone().appendTo('#myform');
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <form id="myform">
    </form>
    <br>
    <button type="button">Submit</button>


    I also invite people to put this code to test the running speed of their algorithm. (used some code from @Diego ZoracKy to make this, thanks!). Have Fun!!!

    /* --------------- Start Speed Test --------------------- */
    let startTime = Math.floor(Date.now());
    let times = 10000; //how many generate call you want?
    let timesHolder = times;
    
    while (times--) {
      let A = randNum();
      let B = randNum();
      let C = randNum();
      let D = randNum();
      generate(A, B, C, D);
      if (times == 0) {
        let totalTime = Math.floor(Date.now()) - startTime;
        let msg = timesHolder + ' Call Finished Within -> ' + totalTime + ' ms <-';
        console.log(msg);
        alert(msg);
      }
    }
    
    function randNum() {
      return Math.floor(Math.random() * (9 - 0 + 1)) + 0;
    }
    /* --------------- END Speed Test --------------------- */
    

    /* --------------- Start Speed Test --------------------- */
    let startTime = Math.floor(Date.now());
    let times = 10000; //how many generate call you want?
    let timesHolder = times;
    
    while (times--) {
      let A = randNum();
      let B = randNum();
      let C = randNum();
      let D = randNum();
      generate(A, B, C, D);
      if (times == 0) {
        let totalTime = Math.floor(Date.now()) - startTime;
        let msg = timesHolder + ' Call Finished Within -> ' + totalTime + ' ms <-';
        console.log(msg);
        alert(msg);
      }
    }
    
    function randNum() {
      return Math.floor(Math.random() * (9 - 0 + 1)) + 0;
    }
    /* --------------- END Speed Test --------------------- */
    
    // return all combination of 4 number (24 combination total)
    function allCom(inputArray) {
      var result = inputArray.reduce(function permute(res, item, key, arr) {
        return res.concat(arr.length > 1 && arr.slice(0, key).concat(arr.slice(key + 1)).reduce(permute, []).map(function(perm) {
          return [item].concat(perm);
        }) || item);
      }, []);
      return result;
    }
    
    // core function to determine the max comb
    function generate(A, B, C, D) {
      let input = [A, B, C, D];
      let allComb = allCom(input);
      let max = '';
      let maxA = [];
    
      allComb.forEach(function(comb, index, arr) {
        if (validCom(comb)) {
          let temp = calValue(comb);
          maxA = temp > max ? comb : maxA;
          max = temp > max ? temp : max;
        }
        if (index == allComb.length - 1) {
          if (max) {
            return 'For ' + JSON.stringify(input) + ' found max comb: ' + maxA[0] + maxA[1] + ':' + maxA[2] + maxA[3];
          }
          return 'Sorry ' + JSON.stringify(input) + ' is not valid';
        }
      });
    }
    
    // check if this array is valid time format, ex [1,2,9,0] false, [2,2,5,5] true
    function validCom(ar) {
      if (ar[0] <= 2 && ((ar[0] == 2 && ar[1] < 4) || (ar[0] != 2 && ar[1] <= 9)) && ar[2] <= 5 && ar[3] <= 9) {
        return true;
      }
      return false;
    }
    
    // calculate the total value of this comb array
    function calValue(ar) {
      return +ar[0] * 36000 + +ar[1] * 3600 + +ar[2] * 10 + +ar[0];
    }

    0 讨论(0)
  • 2020-11-30 02:59
    from itertools  import permutations
    class Solution(object):
        def largestTimeFromDigits(self, A):
            arr = []
            for i in permutations(A,4):
                if int(str(i[0])+str(i[1])) < 24 and int(str(i[2])+ str(i[3])) < 60:
                    arr.append(list(i))
            
            if arr:
                cnt = arr[0]
                for t in arr[1:]:
                    if int(str(t[0])+str(t[1])) > int(str(cnt[0])+ str(cnt[1])):
                        cnt = t
                    elif int(str(t[0])+str(t[1])) == int(str(cnt[0])+ str(cnt[1])):
                        if int(str(t[2])+str(t[3])) > int(str(cnt[2])+ str(cnt[3])):
                            cnt = t
                return str(cnt[0])+ str(cnt[1]) + ":" + str(cnt[2])+ str(cnt[3])  
            else:
                return ""
    
    0 讨论(0)
  • 2020-11-30 03:02

    For places, AB:CD,

    If at any point a condition cannot be fulfilled:
      return NOT POSSIBLE
    
    If there are two numbers greater than 5:
      place the larger in B, smaller in D
    
    for non-filled places from left to right:
      if B > 3:
        place a 1 in A
      else:
        place the largest number smaller than 3 in A
    
      if A is 2:
        place the largest number smaller than 4 in B
      else:
        place the largest number in B
    
      place the largest number smaller than 6 in C
      place the remaining number in D
    
    0 讨论(0)
提交回复
热议问题