knapsack.frink

Download or view knapsack.frink in plain text format


/** 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
}


Download or view knapsack.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 20051 days, 22 hours, 32 minutes ago.