/** This file contains solvers for various knapsack problems. See: Knapsack Problems book: http://www.or.deis.unibo.it/kp.html http://hjemmesider.diku.dk/~pisinger/codes.html https://www.algorist.com/problems/Knapsack_Problem.html https://www.geeksforgeeks.org/0-1-knapsack-problem-dp-10/ https://en.m.wikipedia.org/wiki/Knapsack_problem THINK ABOUT: What to return when no solution is found. */ /** This is a solver for the 0/1 knapsack problem. It takes an array of integer values, array of integer weights, and an integer capacity. It tries to optimize so that the sum of values is the largest for which the sum of the weights is less than or equal to capacity. it returns: [maxvalue, indices, values, weights] where maxvalue: The sum of the values in the best-fitting packing indices : An array of the indices selected values : An array of the values selected weights : An array of the weights selected This algorithm is O(capacity * numElements) in space and time which is inefficient if the capacity is large. Enumerating all the subsets would be O(2^numElements). For example, with a capacity of 6404180 and 24 elements, this algorithm evaluates 153,700,320 cases, where evaluating the 2^24 subsets would be only 16,777,216 cases. Another algorithm should be chosen when the capacity is large. This algorithm is adapted from: https://www.es.ele.tue.nl/education/5MC10/Solutions/knapsack.pdf */ knapsack[values, weights, capacity] := { n = length[values] if length[weights] != n { println["knapsack: length of values does not equal length of weights."] return undef } V = new array[[n+1, capacity+1], 0] keep = new array[[n+1, capacity+1], false] for i = 1 to n { for w = 0 to capacity { if weights@(i-1) <= w and (values@(i-1) + V@(i-1)@(w - weights@(i-1)) > V@(i-1)@w) { V@i@w = values@(i-1) + V@(i-1)@(w-weights@(i-1)) keep@i@w = true } else { V@i@w = V@(i-1)@w keep@i@w = false } } } included = new array K = capacity for i = n to 1 step -1 if keep@i@K == true { included.push[ [i-1, values@(i-1), weights@(i-1)] ] K = K - weights@(i-1) } return concat[V@n@capacity, included.transpose[]] } /** Brute-force algorithm for evaluating the knapsack problem. The time complexity is O(2^numElements) so this is unfeasible if there is a large number of elements. However, as noted above, this is can be significantly more efficient than the above algorithm if the capacity is large. This also works with floating-point numbers or units of measure. It takes an array of values, array of weights, and a capacity. It tries to optimize so that the sum of values is the largest for which the sum of the weights is less than or equal to capacity. it returns: [maxvalue, indices, values, weights] where maxvalue: The sum of the values in the best-fitting packing indices : An array of the indices selected values : An array of the values selected weights : An array of the weights selected */ knapsackBrute[values, weights, capacity] := { n = length[values] if length[weights] != n { println["knapsack: length of values does not equal length of weights."] return undef } // Make a bunch of loops that alternate between false and true. loops = makeArray[[n], [false, true]] // This is to make this work with units of measure. zeroWeight = 0 weights@0 zeroValue = 0 values@0 bestValue = zeroValue bestIndices = undef LOOP: multifor v = loops { weight = zeroWeight value = zeroValue for i = 0 to n-1 if v@i { weight = weight + weights@i if weight > capacity next LOOP i value = value + values@i } if value > bestValue { bestValue = value bestIndices = v.shallowCopy[] } } // Build the result indices = new array outValues = new array outWeights = new array for i = 0 to n-1 if bestIndices@i { indices.push[i] outValues.push[values@i] outWeights.push[weights@i] } return [bestValue, indices, outValues, outWeights] } /** Brute-force algorithm for evaluating the multiple-choice knapsack problem (MCKP). Each weight or value is an array of choices from which exactly one item is selected. See section 2.12 in http://www.or.deis.unibo.it/kp.html The time complexity is O(choices^numElements) so this is unfeasible if there is a large number of elements. This also works with floating-point numbers or units of measure. It takes an array of array of values, array of array of weights, and a capacity. It tries to optimize so that the sum of values is the largest for which the sum of the weights is less than or equal to capacity. it returns: [maxvalue, indices, values, weights] where maxvalue: The sum of the values in the best-fitting packing indices : An array of the indices selected values : An array of the values selected weights : An array of the weights selected */ knapsackMultiBrute[values, weights, capacity] := { n = length[values] if length[weights] != n { println["knapsack: length of values does not equal length of weights."] return undef } // Make a bunch of loops that alternate between the weights loops = new array for i=0 to n-1 loops.push[rangeOf[weights@i]] // This is to make this work with units of measure. zeroWeight = 0 weights@0@0 zeroValue = 0 values@0@0 bestValue = zeroValue bestIndices = undef LOOP: multifor v = loops { weight = zeroWeight value = zeroValue for i = 0 to n-1 { vi = v@i weight = weight + weights@i@vi if weight > capacity next LOOP i value = value + values@i@vi } if value > bestValue { bestValue = value bestIndices = v.shallowCopy[] } } // Build the result indices = new array outValues = new array outWeights = new array for i = 0 to n-1 { bi = bestIndices@i indices.push[bi] outValues.push[values@i@bi] outWeights.push[weights@i@bi] } return [bestValue, indices, outValues, outWeights] } /** Returns all the solutions for the subset-sum problem, that is, finds all the subsets of the input that sum to a desired number. */ subsetSum[vals, target] := { select[toSet[vals].subsets[], {|x, t| sum[x] == t}, target] } /** Returns all the solutions for the subset-product problem, that is, finds all the subsets of the input that multiply to a desired number. */ subsetProduct[vals, target] := { select[toSet[vals].subsets[], {|x, t| product[x] == t}, target] } /** This uses dynamic programming to find a maximum value for the subset-sum problem for integers. That is, it finds the largest subset of weights which sum to target *or less.* arguments: [weights, target] weights: an array of integers target: an integer that we want to sum to. returns [max, weights] Where max: an integer containing the sum weights: an array of values that sum to target or less TODO: Adopt the code from knapsack and simplify it to not pass in weights twice. */ maxSubsetSum[weights, target] := { [maxvalue, indices, values] = knapsack[weights, weights, target] return [maxvalue, values] } /** This is a solver for the "partition problem": given a set of numbers W, it is desired to separate these numbers into two subsets W1 and W2 so that the sums of the numbers in each subset are equal. The partition problem is a special case of the subset sum problem, in which the target sum is one half the sum of the entire set W. args: weights: An array of numeric values returns: an array of pairs [W1, W2] where W1 and W2 are the subsets. */ equalPartitions[weights] := { result = new array sum = sum[weights] / 2 if ! isInteger[sum] return result for sol = subsetSum[weights, sum] { s = deepCopy[weights] for elem = sol s.removeValue[elem] result.push[ [sol, s] ] } return result }