""" Several algorithms approaches to solving the rod cutting problem with Dynamic Programming. See CLRS 15.1 for discussion. Author: Michael Goldwasser Use -h flag for documentation on usage. """ #----------------------------------------------------------------------- def cut_rod(p, n): """Recursive top-down implementation. p is assumed to be an array of size n+1 such that p[k] defines price for length k (with p[0] = 0). """ if n == 0: return 0 else: q = float('-inf') # Consider a first cut of length i, for i from 1 to n inclusive for i in range(1, n+1): q = max(q, p[i] + cut_rod(p, n-i)) return q #----------------------------------------------------------------------- def memoized_cut_rod(p, n, table=None): # create empty table, if none given if table is None: table = [None] * (n+1) # use known answer, if one exists if table[n] is not None: return table[n] # otherwise use recursive formulation if n == 0: q = 0 else: q = float('-inf') # Consider a first cut of length i, for i from 1 to n inclusive for i in range(1, n+1): q = max(q, p[i] + memoized_cut_rod(p, n-i, table)) # memoize answer before returning table[n] = q return q #----------------------------------------------------------------------- def bottom_up_cut_rod(p, n): r = [0] * (n+1) # will be list of maximum revenues. r[0] = 0 for j in range(1, n+1): q = float('-inf') for i in range(1, j+1): q = max(q, p[i] + r[j-i]) r[j] = q return r[n] #----------------------------------------------------------------------- def extended_bottom_up_cut_rod(p, n): """Returns entire table of revenues and additional table of solutions.""" r = [0] * (n+1) # will be list of maximum revenues. r[0] = 0 s = [0] * (n+1) # s[j] is the size of the first piece when starting with j for j in range(1, n+1): q = float('-inf') for i in range(1, j+1): if p[i] + r[j-i] > q: # found a better solution q = p[i] + r[j-i] s[j] = i r[j] = q return r, s #----------------------------------------------------------------------- def build_solution(n, trace): """Build list of rod sizes for optimal solution (given trace from extended algorithm).""" pieces = [] while n > 0: cut = trace[n] pieces.append(cut) n = n - cut return pieces #----------------------------------------------------------------------- if __name__ == '__main__': from optparse import OptionParser, OptionGroup import sys import time import random distributions = { 'gauss' : (lambda:random.gauss(options.density, 0.5)), 'pareto' : (lambda:options.density*random.paretovariate(10)), } algorithms = { 'recursion' : (cut_rod, None), 'memoize' : (memoized_cut_rod, None), 'bottomup' : (bottom_up_cut_rod, extended_bottom_up_cut_rod), } def quit(message=None): if message: print(message) sys.exit(1) parser = OptionParser(usage='usage: %prog [options]') parser.add_option('-n', dest='n', metavar='LENGTH', type='int', default=10, help='length of original rod [default: %default]') parser.add_option('-P', dest='echo', default=False, action='store_true', help='echo price list to console [default: %default]') group = OptionGroup(parser, "Algorithm Options", 'Available algorithms: ' + ', '.join(sorted(algorithms.keys()))) group.add_option('-a', dest='algorithm', default='bottomup', choices=sorted(algorithms.keys()), help='algorithm choice [default: %default]') group.add_option('-V', dest='verbose', default=False, action='store_true', help='generate verbose solution [default: %default]') parser.add_option_group(group) group = OptionGroup(parser, "Pricing Options") group.add_option('-f', dest='file', default=None, help='read (whitespace separated) price list from file') group.add_option('-d', dest='density', type='int', default=10, help='average price per unit [default: %default]') group.add_option('-s', dest='seed', type='int', default=None, help='random seed for generating prices [default: %default]') group.add_option('-r', dest='distribution', default='gauss', choices=sorted(distributions.keys()), help='random distribution function for price densities [default: %default]') parser.add_option_group(group) group = OptionGroup(parser, "Available Distributions", ', '.join(sorted(distributions.keys()))) parser.add_option_group(group) (options,args) = parser.parse_args() if options.n <= 0: quit('n must be positive') if options.seed is None: options.seed = random.randrange(1000000) print('Using seed: {0}'.format(options.seed)) random.seed(options.seed) alg = algorithms[options.algorithm][options.verbose] if alg is None: print('Verbose solutinos not implemented for algorithm {0}'.format(options.algorithm)) sys.exit(1) if options.file is not None: try: raw = open(options.file,'r').read() except IOError: print('Unable to open file {0}'.format(options.file)) sys.exit(1) try: prices = [0] + [int(token) for token in raw.split()] except ValueError: print('Prices must be integers.') sys.exit(1) if len(prices) < 1 + options.n: print('Must specify n prices') sys.exit(1) else: prices = [int(k*distributions[options.distribution]()) for k in range(1 + options.n)] if options.echo: print('Prices: {0}'.format(repr(prices))) result = alg(prices, options.n) if not options.verbose: print('optimal revenue is {0}'.format(result)) else: revenue,solutions = result print('optimal revenue is {0}'.format(revenue[options.n])) pieces = build_solution(options.n, solutions) print('cut into pieces with size: {0}'.format(', '.join(str(p) for p in pieces)))