121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Asteroid partition solver.
|
|
* Verifies that disjoint subsets of asteroid values can sum to each ship capacity.
|
|
*
|
|
* Usage:
|
|
* node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6
|
|
*
|
|
* Output: prints whether a valid partition exists and one example solution.
|
|
*/
|
|
|
|
const args = process.argv.slice(2);
|
|
|
|
function parseArg(flag) {
|
|
const idx = args.indexOf(flag);
|
|
if (idx === -1 || idx + 1 >= args.length) return null;
|
|
return args[idx + 1].split(',').map(Number);
|
|
}
|
|
|
|
const ships = parseArg('--ships');
|
|
const asteroids = parseArg('--asteroids');
|
|
|
|
if (!ships || !asteroids) {
|
|
console.log('Usage: node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Ships: [${ships.join(', ')}] (sum=${ships.reduce((a, b) => a + b, 0)})`);
|
|
console.log(`Asteroids: [${asteroids.join(', ')}] (count=${asteroids.length}, sum=${asteroids.reduce((a, b) => a + b, 0)})`);
|
|
|
|
/**
|
|
* Find disjoint subsets of `available` (by index) that sum to each capacity in `capacities`.
|
|
* Returns array of index arrays, or null if no solution.
|
|
*/
|
|
function findPartition(available, capacities, usedSet = new Set()) {
|
|
if (capacities.length === 0) return [];
|
|
|
|
const target = capacities[0];
|
|
const remaining = capacities.slice(1);
|
|
const remainingSum = remaining.reduce((a, b) => a + b, 0);
|
|
|
|
// Available sum check
|
|
let availSum = 0;
|
|
for (let i = 0; i < available.length; i++) {
|
|
if (!usedSet.has(i)) availSum += available[i];
|
|
}
|
|
if (availSum < target + remainingSum) return null;
|
|
|
|
// Find all subsets summing to target using DFS
|
|
const subset = [];
|
|
|
|
function dfs(startIdx, currentSum) {
|
|
if (currentSum === target) {
|
|
// Try solving remaining capacities
|
|
const newUsed = new Set(usedSet);
|
|
for (const idx of subset) newUsed.add(idx);
|
|
const result = findPartition(available, remaining, newUsed);
|
|
if (result !== null) {
|
|
return [subset.slice(), ...result];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (currentSum > target) return null;
|
|
|
|
for (let i = startIdx; i < available.length; i++) {
|
|
if (usedSet.has(i)) continue;
|
|
if (currentSum + available[i] > target) continue;
|
|
|
|
// Pruning: check if remaining available sum can reach target
|
|
let futureSum = 0;
|
|
for (let j = i; j < available.length; j++) {
|
|
if (!usedSet.has(j) && !subset.includes(j)) futureSum += available[j];
|
|
}
|
|
if (currentSum + futureSum < target) return null;
|
|
|
|
subset.push(i);
|
|
const result = dfs(i + 1, currentSum + available[i]);
|
|
if (result) return result;
|
|
subset.pop();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return dfs(0, 0);
|
|
}
|
|
|
|
// Sort ships descending for better pruning (hardest to fill first)
|
|
const sortedShips = [...ships].sort((a, b) => b - a);
|
|
|
|
console.log(`\nSolving for ships (sorted desc): [${sortedShips.join(', ')}]`);
|
|
|
|
const solution = findPartition(asteroids, sortedShips);
|
|
|
|
if (solution) {
|
|
console.log('\n✓ SOLVABLE! Solution found:');
|
|
// Map back to original ship order
|
|
const shipOrder = ships.map((cap, idx) => ({ cap, idx }))
|
|
.sort((a, b) => b.cap - a.cap);
|
|
|
|
for (let i = 0; i < sortedShips.length; i++) {
|
|
const indices = solution[i];
|
|
const values = indices.map(j => asteroids[j]);
|
|
const origShipIdx = shipOrder[i].idx;
|
|
console.log(` Ship ${origShipIdx + 1} (capacity ${sortedShips[i]}): asteroids [${values.join(' + ')}] = ${values.reduce((a, b) => a + b, 0)} (indices: ${indices.join(',')})`);
|
|
}
|
|
|
|
const usedIndices = new Set(solution.flat());
|
|
const unused = asteroids.map((v, i) => ({ v, i })).filter(x => !usedIndices.has(x.i));
|
|
if (unused.length > 0) {
|
|
console.log(` Unused asteroids: [${unused.map(x => `${x.v}(idx ${x.i})`).join(', ')}]`);
|
|
}
|
|
} else {
|
|
console.log('\n✗ NO SOLUTION found.');
|
|
console.log(` Ship sum: ${ships.reduce((a, b) => a + b, 0)}, Asteroid sum: ${asteroids.reduce((a, b) => a + b, 0)}`);
|
|
if (asteroids.reduce((a, b) => a + b, 0) < ships.reduce((a, b) => a + b, 0)) {
|
|
console.log(' Reason: asteroid sum < ship sum (mathematically impossible)');
|
|
}
|
|
}
|