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
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();
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";
}
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 --------------------- */
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>
/* --------------- 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];
}
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 ""
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