function minimize(cars, people) {
cars = Object.entries(cars)
.map(([seats, price]) => ({ seats: +seats, price }));
const chunk = lcm(...cars.map( ({seats}) => seats ));
const memo = [{
carCounts: Array(cars.length).fill(0),
price: 0,
freeSeats: 0
}];
for (let i = 1, until = Math.min(chunk*2, people); i <= until; i++) {
if (memo[i-1].freeSeats) {
memo[i] = Object.assign({}, memo[i-1]);
memo[i].freeSeats--;
} else {
const best = cars.reduce( (best, car, seq) => {
const other = memo[Math.max(0, i - car.seats)],
price = car.price + other.price;
return price < best.price ? { price, car, other, seq } : best;
}, { price: Infinity } );
memo[i] = {
carCounts: best.other.carCounts.slice(),
price: best.price,
freeSeats: best.other.freeSeats + Math.max(0, best.car.seats - i)
};
memo[i].carCounts[best.seq]++;
}
}
let result;
if (people > 2 * chunk) {
const times = Math.floor(people / chunk) - 1;
result = memo[chunk + people % chunk].carCounts.map( (count, seq) =>
count + times * (memo[2*chunk].carCounts[seq] - memo[chunk].carCounts[seq])
);
} else {
result = memo[people].carCounts;
}
return Object.assign(...result
.map( (count, seq) => ({ [cars[seq].seats]: count })));
}
function lcm(a, b, ...args) {
return b === undefined ? a : lcm(a * b / gcd(a, b), ...args);
}
function gcd(a, b, ...args) {
if (b === undefined) return a;
while (a) {
[b, a] = [a, b % a];
}
return gcd(b, ...args);
}
function processInput() {
let cars;
try {
cars = JSON.parse(inpCars.value);
} catch(e) {
preOut.textContent = 'Invalid JSON';
return;
}
const result = minimize(cars, +inpPeople.value);
preOut.textContent = JSON.stringify(result);
}
inpPeople.oninput = inpCars.oninput = processInput;
processInput();
Car definition (enter as JSON):
<input id="inpCars" value='{ "3": 1, "4": 1.4, "8": 2.2 }' style="width:100%"><p>
People: <input id="inpPeople" type="number" value="13" min="0" style="width:6em"><p>
Optimal Car Assignment (lowest price):
<pre id="preOut"></pre>