#!/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)'); } }