Knapsack Algorithm

Daily fantasy lineups contain a set number of players: for example, DraftKings has 1 QB, 2 RB, 3 WR, 1 TE, 1 flex, and 1 defense. This is the perfect setup for the multiple-choice knapsack problem (see http://www.diku.dk/users/pisinger/95-1.pdf).

We will use a modified version of the dynamic programming method used to solve the regular knapsack problem in order to solve the multiple-choice knapsack problem. We deal with the problem of flex picks by solving problems with restrictions of 3 RB, 4WR, and 2 flex, respectively, and then picking the highest-scoring solution.

The code for the algorithm is as follows. The remove_dom function is an auxiliary used to remove dominated items to speed up the algorithm and return an approximation. We do not use it.

# objects is an array of items, each item has a name, weight, and price
def remove_dom(objects):
    # sort objects in increasing order of weight
    objects = objects[objects[:,1].argsort()]
    # iterate through all pairs of objects, if there is an object i such that w_i>w_j but p_i<p_j,
    # remove object i because it is dominated by object j
    new_objects = np.empty([len(objects[0])])
    for i in range(len(objects)):
        is_dom = False
        for j in range(i):
            if objects[i][2] < objects[j][2]:
                is_dom = True
        if not is_dom:
            new_objects = np.vstack((new_objects,objects[i]))
    return new_objects[1:]

class Contents:
    items = []
    types = []
    value = 0
    def __init__(self, num_types):
        self.types = [0 for i in range(num_types)]
    def add(self, object_num, object_value, object_type):
        new_contents = Contents(len(self.types))
        new_contents.items = list(self.items)
        new_contents.items.append(object_num)
        new_contents.types = list(self.types)
        new_contents.types[object_type] += 1
        new_contents.value = self.value
        new_contents.value += object_value
        return new_contents

# objects array has form [name, weight, value, type]
# cat_indices is a list of the last indices for each category; for example, if cat_indices=[1,2],
# item 0 would be in category 0, item 1 would be in category 1, and the rest of the items would be in category 2
# cap_cap is a list of the capacity, or maximum number of items, in each category
def multi_knapsack(objects, weight_limit, cat_indices, cat_cap):
    assert len(cat_indices) == len(cat_cap)
    cat = len(cat_indices) # number of categories
    
    # don't remove dominated objects; this makes it harder to exactly fulfill the salary cap
    '''
    split_arr = np.split(objects, cat_indices)
    for i in range(len(split_arr)-1):
        split_arr[i] = remove_dom(split_arr[i])
        after_index = len(split_arr[i])
        # update cat_indices to account for dropped items
        if i==0:
            cat_indices[i] = after_index
        else:
            cat_indices[i] = cat_indices[i-1] + after_index
    new_objects = np.vstack(split_arr[:-1])
    '''
    
    new_objects = objects
    max_array = [[[Contents(len(cat_cap)) for k in range(cat_cap[int(new_objects[j][3])]+1)] for i in range(weight_limit+1)] for j in range(len(new_objects))]
    max_array = [[[Contents(len(cat_cap))] for i in range(weight_limit+1)]] + max_array
    
    for i in range(len(new_objects)):
        weight = int(new_objects[i][1])
        value = int(new_objects[i][2])
        object_type = int(new_objects[i][3])
        last_same_type = False
        if i != 0:
            last_same_type = object_type == int(new_objects[i-1][3])
        
        for w in range(1,weight_limit+1):
            for n in range(cat_cap[object_type]+1):
                if weight  max_array[i][w][n].value:
                            max_array[i+1][w][n] = max_array[i][w-weight][n-1].add(i,value,object_type)
                        else:
                            max_array[i+1][w][n] = max_array[i][w][n]
                    elif n==1:
                        max_array[i+1][w][n] = max_array[i][w-weight][-1].add(i,value,object_type)
                else:
                    if last_same_type:
                        max_array[i+1][w][n] = max_array[i][w][n]
                    else:
                        max_array[i+1][w][n] = max_array[i][w][-1]
                    
    return [new_objects[i][0] for i in max_array[len(new_objects)][weight_limit][-1].items]
Knapsack Algorithm

Leave a comment