Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/util/knapsack.ts
Views: 687
/*1This is based on2https://gist.github.com/frobnitzem/28707410b81870e88925097fdfe1f85b3but I converted it to modern typescript, added unit tests,4proper style conventions, etc.56Yes, I looked all over npm and couldn't find anything good.78There's no license but the code is very short and it's9just passed around implementing a very standard algorithm.1011Let's just call it BSD 3-clause going forward.12TODO: make this an NPM package.1314Solve the knapsack problem using recursive descent.15This wraps the actual solver below with a nice interface.16It also handles non-integer cost, but then the complexity17scales with the precision of the cost.1819items is an object of named {cost, benefit} objects, e.g.20{ banana: {cost:5, benefit:33},21apple: {cost:1, benefit:12},22kiwi: {cost:1, benefit:7}23}2425maxCost is the maximum cost allowed in the knapsack.26*/2728export type Items = { [key: string]: { cost: number; benefit: number } };2930export default function knapsack(31items: Items,32maxCost: number33): {34items: string[]; // names of the items to include35cost: number; // total cost of this choice36benefit: number; // total benefit of this choice37} {38// c = cost; b = benefit39const arr: { c: number; b: number; n: string }[] = [];40for (const key in items) {41arr.push({ c: items[key].cost, b: items[key].benefit, n: key });42}43// Sort descending for a 'greedy' initial search order.44arr.sort((x, y) => {45if (x.b == y.b) {46// lower cost breaks tie47return x.c - y.c;48}49return y.b - x.b; // Consider highest value first.50});51const memo = {};52let ret = knap(arr, maxCost, 0, memo);53// console.log(Object.keys(memo).length + " calls.");54for (let i = 0; i < ret[0].length; i++) {55// Name the winners.56ret[0][i] = arr[ret[0][i]].n;57}5859return { items: ret[0], cost: ret[1], benefit: ret[2] };60}6162//Knapsack algorithm63//==================64// wikipedia: [Knapsack (0/1)](http://en.wikipedia.org/wiki/Knapsack_problem#0.2F1_Knapsack_Problem)65// Given a set `[{cost:Number, benefit:Number}]`,66// find the maximum benefit possible provided that the cost67// must remain below or equal to the capacity, "maxCost",68// AND the benefit must be at or equal to "minBen".69// **params**:70// `items` : [{w:Number, b:Number}],71// `maxCost` : Number,72// `minBen` : Number73// **returns**:74// An object containing `maxValue` and `set`7576// Solve a knapsack problem. This routine is meant77// to be called by knapsack (above).78// For efficiency, items should be sorted by79// descending benefit.80//81// returns [items], cost, benefit82//83// Note:84// This method uses dead-end elimination to avoid solving85// extra sub-problems.86// It uses a cache of calls at each (#items, maxCost)87// to avoid solving similar sub-problems multiple times.88// { (#items,maxCost) : [minBen_req, items, maxCost_found, minBen_found] }89function knap(items, maxCost, minBen, memo) {90let best: number[] = [];91let cost = 0;92let remain_ben = 0;93if (minBen < 0) minBen = 0;94let minBen_inp = minBen; // save the lower bound we started with for caching purposes9596let note = items.length + " " + maxCost; // attempt to lookup the answer97if (note in memo) {98if (memo[note][0] <= minBen || memo[note][3] >= minBen) {99//console.log("re-used: " + note);100return memo[note].slice(1);101}102}103104for (let i = 0; i < items.length; i++) {105// determine remaining possible benefit106if (items[i].c <= maxCost) remain_ben += items[i].b;107}108109for (let i = 0; i < items.length; i++) {110if (items[i].c > maxCost) {111continue;112} // Can't include.113114if (remain_ben < minBen) {115// Early termination check.116break;117}118remain_ben -= items[i].b;119120let ret = knap(121items.slice(i + 1),122maxCost - items[i].c,123minBen - items[i].b,124memo125);126if (ret[2] + items[i].b > minBen) {127// Found a better subproblem solution.128best = ret[0].map(function (j) {129return i + j + 1;130});131best.push(i);132cost = ret[1] + items[i].c;133minBen = ret[2] + items[i].b; // up the ante134}135}136137if (best.length == 0) {138memo[note] = [minBen_inp, [], 0, 0];139} else {140memo[note] = [minBen_inp, best, cost, minBen];141}142143//console.log(note, memo[note]);144return memo[note].slice(1);145}146147148