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
This is what I came up with. Hardly elegant, I might try to tidy it make it a bit more efficient. I have a feeling a brute force approach would be the cleanest and most efficient way to do it. This is a mess.
// w: highest value 2 or less
// UNLESS: 1 of b, c, or d are less than 3 while the other two are greater than 7
// x: highest value
// UNLESS: last was 2 then highest value less than 2
// y: highest value less than 5
// z: highest remaining value
function findhighestwhere(array, condition) {
let res = null
let val = -1
let i = 0
for (let x of array) {
if (x !== null && condition(x) && x > val) {
res = i
val = x
}
i++
}
// console.log(`Test index: ${res} \n Test value: ${val}`)
return res
}
function generate(a,b,c,d) {
// console.log(`Testing: ${a}${b}${c}${d}`)
let array = [a,b,c,d]
let wi = findhighestwhere(array, x => x <= 2)
// That one pain in the conditional edge-case
if ( array[wi] == 2 ) {
// console.log(`Encountered First Position 2 Checking for Edge Case`)
let i = 0
let lowcount = 0
let highcount = 0
for (let x of array) {
if ( i != wi && x <= 3 ) lowcount++
if ( i != wi && x >= 6 ) highcount++
i++
}
if ( lowcount == 1 && highcount == 2 ) {
// console.log(`Edge Case Encountered`)
wi = findhighestwhere(array, x => x <= 1)
}
}
if ( wi === null ) return false
let w = array[wi]
// console.log(`W: ${w}`)
array[wi] = null
if ( w == 2 ) {
var xi = findhighestwhere(array, x => x <= 3)
} else {
var xi = findhighestwhere(array, x => true)
}
if ( xi === null ) return false
let x = array[xi]
// console.log(`X: ${x}`)
array[xi] = null
let yi = findhighestwhere(array, x => x <= 5)
if ( yi === null ) return false
let y = array[yi]
// console.log(`Y: ${y}`)
array[yi] = null
let zi = findhighestwhere(array, x => true)
if ( zi === null ) return false
let z = array[zi]
// console.log(`Z: ${z}`)
array[zi] = null
return `${w}${x}:${y}${z}`
}
console.log(`6520: ${generate(6,5,2,0)}`) // 6520: 20:56
console.log(`3950: ${generate(3,9,5,0)}`) // 3950: 09:53
console.log(`7638: ${generate(7,6,3,8)}`) // 7638: false
console.log(`1727: ${generate(1,7,2,7)}`) // 1727: 17:27
I think this method is called Brute Force. Took test samples from @Dummy's answer.
<script>
function generate(A, B, C, D) {
var result = -1
var v = [A, B, C, D]
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) if (j != i) {
for (k = 0; k < 4; k++) if (k != j && k != i) {
for (m = 0; m < 4; m++) if (m != k && m != j && m != i) {
if (v[i]*10 + v[j] < 24 && v[k]*10 + v[m] < 60) { //legal time
if (v[i]*1000 + v[j]*100 + v[k]*10 + v[m] > result) {
result = v[i]*1000 + v[j]*100 + v[k]*10 + v[m]
}
}
}
}
}
}
return result >= 0? Math.floor(result/100) + ':' + result%100: 'NOT POSSIBLE'
}
console.log('generate(1,7,2,7)', generate(1,7,2,7))
console.log('generate(6,5,2,0)', generate(6,5,2,0))
console.log('generate(3,9,5,0)', generate(3,9,5,0))
console.log('generate(7,6,3,8)', generate(7,6,3,8))
console.log('generate(0,1,2,3)', generate(0,1,2,3))
console.log('generate(1,1,1,2)', generate(1,1,1,2))
console.log('generate(1,1,1,1)', generate(1,1,1,1))
console.log('generate(5,6,7,8)', generate(5,6,7,8))
console.log('generate(2,9,3,1)', generate(2,9,3,1))
</script>
Here is a non brute force solution that I came up with. Check out the comments in the code to see how it works. If any of it is unclear I can help clarify.
function generate(A, B, C, D) {
vals = [A, B, C, D];
counts = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (i = 0; i < vals.length; i++) {
for (j = vals[i]; j < counts.length; j++) counts[j]++;
}
// counts is now populated with the number of values less than or equal to the index it belongs to
// so counts[2] is the total number of 0's, 1's and 2's
if (counts[2] === 0) return 'NOT POSSIBLE';
// if there are no 0's and 1's, then it must start with 2
mustStartWith2 = counts[1] === 0;
if (mustStartWith2 && counts[3] === 1) return 'NOT POSSIBLE';
// We want a count of the number of free digits that are 5 or less (for the minute digit)
numbersAvailableForMinute = counts[5] - (mustStartWith2 ? 2 : 1);
if (numbersAvailableForMinute === 0) return 'NOT POSSIBLE';
// we now know that it is a valid time
time = [0, 0, 0, 0];
// we also know if it starts with 2
startsWith2 = mustStartWith2 || (numbersAvailableForMinute >= 2 && counts[2] > counts[1]);
// knowing the starting digit, we know the maximum value for each digit
maxs = startsWith2 ? [2, 3, 5, 9] : [1, 9, 5, 9];
for (i = 0; i < maxs.length; i++) {
// find the first occurrence in counts that has the same count as the maximum
time[i] = counts.indexOf(counts[maxs[i]]);
// update counts after the value was removed
for (j = time[i]; j < counts.length; j++) counts[j]--;
}
// create the time
return time[0]+""+time[1]+":"+time[2]+""+time[3];
}
Added executable snippet and some test cases
function generate(A, B, C, D) {
var combinations = []
arguments = Array.from(arguments)
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (i !== j) {
var num = +(arguments[i] + '' + arguments[j])
if (num <= 59 && combinations.indexOf(num) === -1)
combinations.push(num)
}
}
}
combinations.sort((a, b) => a - b);
var hours = combinations.filter(hour => hour <= 23);
for (var i = hours.length - 1; i >= 0; i--) {
for (var j = combinations.length - 1; j >= 0; j--) {
if (computeMax(hours[i], combinations[j], arguments))
return hours[i] + ':' + combinations[j]
}
}
return 'not possible'
}
function computeMax(maxHour, maxMinute, args) {
var minute = String(maxMinute)
var hour = String(maxHour)
for (var k = 0; k < minute.length; k++)
if (hour.indexOf(minute[k]) > -1 && args.indexOf(+minute[k]) === args.lastIndexOf(+minute[k]))
return false
return true
}
console.log('generate(1,7,2,7)', generate(1,7,2,7))
console.log('generate(6,5,2,0)', generate(6,5,2,0))
console.log('generate(3,9,5,0)', generate(3,9,5,0))
console.log('generate(7,6,3,8)', generate(7,6,3,8))
console.log('generate(0,1,2,3)', generate(0,1,2,3))
console.log('generate(1,1,1,2)', generate(1,1,1,2))
console.log('generate(1,1,1,1)', generate(1,1,1,1))
console.log('generate(5,6,7,8)', generate(5,6,7,8))
console.log('generate(2,9,3,1)', generate(2,9,3,1))
UPDATED
Just try to find some way to improve the performance, the new idea is inspired by counting sort.
Simply count the number of each digit, then base on the following chain of dependencies, brute find the optimal possibilities. The answer would be one of those, highest priority first:
/* --------------- Start Speed Test --------------------- */
var startTime = Math.floor(Date.now());
var times = 10000; //how many generate call you want?
var timesHolder = times;
while (times--) {
var A = randNum();
var B = randNum();
var C = randNum();
var D = randNum();
generate(A, B, C, D);
if (times == 0) {
var totalTime = Math.floor(Date.now()) - startTime;
var 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 --------------------- */
function generate(A,B,C,D){
var cnt = [0,0,0,0,0,0,0,0,0,0], ans = ['', ''];
cnt[A]++; cnt[B]++; cnt[C]++; cnt[D]++;
function gen(part, max){
for(var i=max; i>=0; i--) if(cnt[i]){
ans[part] += i;
cnt[i]--;
return 1;
}
return 0;
}
function rollback(first){
cnt[first]++;
for(var i in ans[0]) cnt[ans[0][i]]++;
for(var i in ans[1]) cnt[ans[1][i]]++;
ans[0] = ans[1] = '';
}
/*** Main logic, based on the chain of dependencies ***/
if(cnt[2]){
cnt[2]--;
if(!gen(0, 3) || !gen(1,5) || !gen(1,9)) rollback(2);
else return '2' + ans[0] + ':' + ans[1];
}
if(cnt[1]){
cnt[1]--;
if(!gen(0, 9) || !gen(1,5) || !gen(1,9)) rollback(1);
else return '1' + ans[0] + ':' + ans[1];
}
if(cnt[0]){
cnt[0]--;
if(!gen(0, 9) || !gen(1,5) || !gen(1,9)) rollback(0);
else return '0' + ans[0] + ':' + ans[1];
}
return 'NOT POSSIBLE';
}
console.log(generate(1,7,2,7));
console.log(generate(0,0,2,9));
console.log(generate(6,5,2,0));
console.log(generate(3,9,5,0));
console.log(generate(7,6,3,8));
console.log(generate(0,0,0,0));
console.log(generate(9,9,9,9));
console.log(generate(1,2,3,4));
I recently was working on the same problem (with 6 digits though) and came up with this non-brute solution:
#include <iostream>
#include <iomanip>
int numbers[6] = { 0, 0, 0, 0, 0, 0 };
int input[6] = { 1, 7, 3, 3, 4, 1 };
void buildHistogram() {
for (int i = 0; i < 6; ++i) {
numbers[input[i]]++;
}
}
int getMaxNotExceeding(int number) {
for (int i = number; i >= 0; --i) {
if (numbers[i] > 0) {
numbers[i]--;
return i;
}
}
throw std::exception("CANNOT CREATE TIME");
}
int main() {
try {
buildHistogram();
int hours = (getMaxNotExceeding(2) * 10);
if (hours < 20) {
hours += getMaxNotExceeding(9);
} else {
hours += getMaxNotExceeding(3);
}
int minutes = (getMaxNotExceeding(5) * 10) + getMaxNotExceeding(9);
int seconds = (getMaxNotExceeding(5) * 10) + getMaxNotExceeding(9);
if (seconds > 59 || minutes > 59 || hours > 23) {
throw std::exception("CANNOT CREATE TIME");
}
std::cout.fill('0');
std::cout << std::setw(2) << hours << ':' << std::setw(2) << minutes << ':' << std::setw(2) << seconds << std::endl;
} catch(const std::exception& ex) {
std::cout << ex.what() << std::endl;
}
return 0;
}