#!/usr/bin/python

"""
Implementation of a Subset Sum / Knapsack implementation.
"""
import sys
import random


SMALL = 30



def solve(data, K):
    n = len(data)
    print "n:",n
    print "Target sum:",K

    maxLength = len(str(max(data)))

    if n <= SMALL:
        print "Original values:",data

    data.insert(0,"Fake")  # want to match book, which indexes elements starting with 1

    # initialize table
    opt = []
    for i in range(0,n+1):
        opt.append(  (K+1) * [0] )  # row of zeroes

    # fill in row-by-row
    for i in range(1, n+1):
        for w in range(0,K+1):
            choice = opt[i-1][w]   # if i'th element is ignored
            if data[i] <= w:
                alternate = data[i] + opt[i-1][w-data[i]]
                if alternate > choice:
                    choice = alternate
            opt[i][w] = choice
            

    print
    print "Maximum possible is", opt[n][K]
    print

    if n <= SMALL:
        solution = []
        row = n
        col = K
        while row > 0:
            if opt[row][col] != opt[row-1][col]:
                solution.append(data[row])
                col = col-data[row]
            row -=1
        solution.reverse()  # to match original order
        print 'Solution is', solution
        if sum(solution) != opt[n][K]:
            print 'Sanity check failed.  Sum of proposed solution is', sum(solution)

    if n <= SMALL:
        # echo table
        print
        for row in range(n,-1,-1):
            print str(row).rjust(len(str(n))) + ":",
            for entry in opt[row]:
                print str(entry).rjust(maxLength),
            print
    
        print "-" * (len(str(n)) + 2 + (maxLength+1)*(K+1))
        print " " * (1+len(str(n))),
        for w in range(0,K+1):
            print str(w).rjust(maxLength),
        print



    data.pop(0)  # remove the fake element
        
    



def generate(N, K, seed):
    """
    Generates N random values, uniformly in the range (0, 4*K/N).

    The reason for the choice 4*K/N is so that the expected overall
    sum of the data is 2K, and therefore that typical solutions to the
    subset sum problem will involve perhaps half of the elements.
    """
    if seed == None:   # could leave as none to get system clock, but we want to echo the choice
        seed = random.randint(0,1000000000)
        print "Using seed:",seed
    
    random.seed(seed)
    data = []
    while len(data) < N:
        data.append(random.randint(1,4*K/N))
    return data

    


def main(argv):
    """
    Usage:  filename
    Usage:  N  K  [seed]
    """

    usage = "subset.py filename\nsubset.py N K [seed]"

    if len(argv)==1:
        try:
            f = file(argv[0])
            items = []
            for line in f:
                try:
                    items.append(int(line))
                except ValueError:
                    print "line '%s' ignored"%line
            K = items.pop(0)
                

                
        except IOError:
            print "unable to open file '%s'"%argv[0]
            print usage
    else:
        if len(argv)<2:
            print usage
            return

        try:
            N = int(argv[0])
            if N < 1:
                raise ValueError
        except ValueError:
            print "Value of N must be positive integer.  '%s' invalid."%argv[0]
            return

        try:
            K = int(argv[1])
            if K < 1:
                raise ValueError
        except ValueError:
            print "Value of K must be positive integer.  '%s' invalid."%argv[1]
            return

        seed = None
        if len(argv) > 2:
            try:
                seed = int(argv[2])
            except ValueError:
                print "Value of seed must be integral.  '%s' invalid."%argv[2]


        items = generate(N,K,seed)

    solve(items, K)



if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
    

