Knapsack

August 23, 2011

Our function takes a vector of objects, where each object is itself a three-slot vector of name, value and weight, and a capacity, and returns both the value achieved and a list of the object names included in the solution:

(define (knapsack objs cap) ; objs is vector of #(name value weight)
  (let* ((len (vector-length objs))
         (vs (make-matrix (+ len 1) (+ cap 1) 0))
         (ns (make-matrix (+ len 1) (+ cap 1) #f)))

    (define (n i) (vector-ref (vector-ref objs (- i 1)) 0))
    (define (v i) (vector-ref (vector-ref objs (- i 1)) 1))
    (define (w i) (vector-ref (vector-ref objs (- i 1)) 2))
    (define (mv r c) (matrix-ref vs r c))
    (define (mn r c) (matrix-ref ns r c))
    (define (mv! r c x) (matrix-set! vs r c x))
    (define (mn! r c x) (matrix-set! ns r c x))

    ; (do ((c 0 (+ c 1))) ((< len c)) (mv! 0 c 0))

    (do ((r 1 (+ r 1))) ((< len r))
      (do ((c 0 (+ c 1))) ((< cap c))
        (cond ((and (<= (w r) c)
                    (< (mv (- r 1) c)
                       (+ (v r) (mv (- r 1) (- c (w r))))))
                (mv! r c (+ (v r) (mv (- r 1) (- c (w r)))))
                (mn! r c #t))
              (else (mv! r c (mv (- r 1) c)) (mn! r c #f)))))

    (let loop ((r len) (k cap) (ks '()))
      (cond ((zero? r) (values (mv len cap) ks))
            ((mn r k) (loop (- r 1) (- k (w r)) (cons (n r) ks)))
            (else (loop (- r 1) k ks))))))

The function begins by computing the number of objects and creating matrices to store the accumulating values, vs, and the object names included in the accumulating solution, ns. Then follow several defines that help with readability. The commented-out line is included to remind the reader what is happening, but it’s not necessary because the value was included in the initialization. The main body of the function is the nested do loops on the rows r and columns c of the matrices; the cond implements the max of the recurrence expression. The final let loop accumulates the object names included in the solution. Here are some examples:

> (knapsack #(#(a 10 5) #(b 40 4) #(c 30 6) #(d 50 3)) 10)
90
(b d)
> (knapsack #(#(a 4 12) #(b 2 1) #(c 10 4) #(d 2 2) #(e 1 1)) 15)
15
(b c d e)

We used the matrix functions from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/sZVbIi97.

Pages: 1 2

11 Responses to “Knapsack”

  1. Mike said

    Python 3.

    from collections import namedtuple
    from operator import attrgetter
    
    Item = namedtuple('Item','name value weight')
    Node = namedtuple('Node','value items')
    
    def knapsack(items, capacity):
        m = [Node(0,[])] * (capacity + 1)
    
        for item in sorted(items, key=operator.attrgetter('weight')):
            for w in range(capacity, item.weight-1, -1):
    
                prev = m[w - item.weight]
    
                if prev.value + item.value > m[w].value:
                    m[w] = Node(prev.value + item.value, prev.items + [item.name])
    
        return m[-1].value, sorted(m[-1].items)
    
  2. Mike said

    ‘items’ is a list of objects that have ‘name’, ‘value’, and ‘weight’ attributes. For testing, I used the Item() namedtuple above.

    Example,

    from random import randint
    
    items = [Item(c, randint(1,50), randint(1,15)) for c in 'abcdefgh']
    #items = [
    # Item(name='a', value=21, weight=10),
    # Item(name='b', value=31, weight=10),
    # Item(name='c', value=13, weight=1),
    # Item(name='d', value=9, weight=6),
    # Item(name='e', value=2, weight=15),
    # Item(name='f', value=10, weight=2),
    # Item(name='g', value=24, weight=4),
    # Item(name='h', value=15, weight=4)]
    
    knapsack(items, 20)  
    # (83, ['b', 'c', 'g', 'h'])
    
  3. Axio said

    What is W? Why do you start with V[0,w] and not, say, V[i,w], which would make more sense since you wrote “1 ≤ i ≤ n”

  4. programmingpraxis said

    wi is the weight of the ith object. V[0,w] is the base of the recurrence expression, and hence must be calculated first.

  5. Axio said

    I was asking about the capital W, which is defined nowhere. I guess it’s the maximum weight one can bear… Also, it should be “all objects 1≤i≤n” rather than “all sets of objects 1≤i≤n” shouldn’t it?
    Anyway, I have a solution, but it’s too ugly for posting :)

  6. Axio said
    (defstruct item value weight name)
    
    (defparameter *my-items*
          (vector
           (make-item :value 21 :weight 10 :name 'a)
           (make-item :value 31 :weight 10 :name 'b)
           (make-item :value 13 :weight 1 :name 'c)
           (make-item :value 9 :weight 6 :name 'd)
           (make-item :value 2 :weight 15 :name 'e)
           (make-item :value 10 :weight 2 :name 'f)
           (make-item :value 24 :weight 4 :name 'g)
           (make-item :value 15 :weight 4 :name 'h)))
    
    (defmacro val (i) `(item-value (aref items (1- ,i))))
    (defmacro wei (i) `(item-weight (aref items (1- ,i))))
    (defmacro nam (i) `(item-name (aref items (1- ,i))))
    
    (defun ks (items max-weight)
      (let ((items (sort items #'< :key #'item-value))
            (mat (make-array (list (1+ (array-dimension items 0))
                                   (1+ max-weight))
                             :initial-element '(0))))
        (loop for i from 1 upto (array-dimension items 0)
           do (loop for w from max-weight downto 0
                 do (let ((pred (aref mat (1- i) w)))
                      (setf (aref mat i w)
                            (if (or (< (- w (wei i)) 0) ;; to heavy
                                    (> (car pred) ;; no gain
                                       (+ (val i)
                                          (car (aref mat (1- i) (- w (wei i)))))))
                                pred
                                (cons (+ (val i)
                                         (car (aref mat (1- i) (- w (wei i)))))
                                      (cons (nam i)
                                            (cdr (aref mat (1- i) (- w (wei i)))))))))))
        (format t "~a~%" mat)
        (aref mat (array-dimension items 0) max-weight)))
    
  7. programmingpraxis said

    W is the capacity of the knapsack.

  8. ohwow said


    (define values
    #(2 4 5 1 3))
    (define weights
    #(3 2 2 3 4))

    (define (knapsack w i)
    (cond
    ((or (= w 0)
    (= i 0)) 0)
    ((> (vector-ref weights (sub1 i)) w)
    (knapsack w (sub1 i)))
    (else
    (max (knapsack w (sub1 i))
    (+ (vector-ref values (sub1 i))
    (knapsack (- w (vector-ref weights (sub1 i)))
    (- i 1)))))))

  9. ohwow said
    (define values 
      #(2 4 5 1 3))
    (define weights
      #(3 2 2 3 4))
    
    (define (knapsack w i)
      (cond
        ((or (= w 0)
             (= i 0)) 0)
        ((> (vector-ref weights (sub1 i)) w)
         (knapsack w (sub1 i)))
        (else
         (max (knapsack w (sub1 i))
              (+ (vector-ref values (sub1 i))
                 (knapsack (- w (vector-ref weights (sub1 i)))
                           (- i 1)))))))
    
    
    

    Sorry for poor formating

  10. Why must the objects be sorted by weight? Your examples don’t obey this constraint, and your program doesn’t sort the objects. Anyway, I didn’t bother with the matrix and just used recursion and memoization (like in the Word Breaks problem):

    #lang racket
    (require rackunit)
    
    (struct obj (value weight))
    
    (define (max-value objs max-weight)
      (if (null? objs)
          0
          (match-let (((list-rest (struct obj (value weight)) rest) objs))
            (if (> weight max-weight)
                (max-value rest max-weight)
                (max (+ value (max-value rest (- max-weight weight)))
                     (max-value rest max-weight))))))
    
    (define (memoize f)
      (let ((dict (make-hash)))
        (λ args (dict-ref! dict args (λ () (apply f args))))))
    
    (set! max-value (memoize max-value))
    
    (check-equal? (max-value (list (obj 2 2)) 2) 2)
    (check-equal? (max-value (list (obj 2 2)) 1) 0)
    (check-equal? (max-value (list (obj 2 2) (obj 1 1) (obj 3 3)) 6) 6)
    (check-equal? (max-value (list (obj 2 2) (obj 1 1) (obj 3 3)) 5) 5)
    (check-equal? (max-value (list (obj 10 5) (obj 40 4) (obj 30 6) (obj 50 3)) 10) 90)
    (check-equal? (max-value (list (obj 4 12) (obj 2 1) (obj 10 4) (obj 2 2) (obj 1 1)) 15) 15)
    
  11. […] reduces the multiplication of the factors to addition of their logarithms, which means that the knapsack algorithm can be used to find the greatest sum less than the logarithm of the square […]

Leave a comment