0
$\begingroup$

I'm writing a Python program to calculate the maximum value of a polynomial $p * (1 + (d * (1 + (o * (1 + g))))$, subject to the constraints that $p$, $d$, $o$ and $g$ are all positive integers, and $p + 3*d + 6*o + 15*g \le s$, where $s$ is a user-defined constant. Right now, the program uses dynamic programming to calculate the optimal value of $(o * (1 + g))$ and $(d * (1 + (o * (1 + g)))$, given each possible value of p. I've gotten suggestions for improving the performance of the code at Code Review, but I'm wondering if there's some mathematical way of optimizing my algorithm.

Code:

'''
This program calculates the optimal number of game upgrades to purchase
given a certain number of points to spend on upgrades

Upgrades cost:
    - pennies:  1
    - dimes:    3
    - dollars:  6
    - gold:    15

The upgrade bonus can be calculated as:
    pennies + pennies * dimes + pennies * dimes * dollars 
        + pennies * dimes * dollars * gold
or alternatively:
    pennies * (1 + dimes * (1 + dollars * (1 + gold)))
'''

from functools import cache
import time

'''
Input: points is the number of points to spend on pennies, dimes, dollars, and gold combined
Output: returns a nested tuple containing 
    ((pennies, dimes, dollars, gold), total bonus)
'''
def get_max_score(points):
    last_max_score = -1
    last_max_combo = (0, 0, 0, 0)
    # we decrement by 3 instead of 1, because the cost of all 
    # upgrades other than pennies is a multiple of 3.
    # Therefore, the number of upgrades other than pennies that
    # we can buy only changes every 3 pennies.
    for pennies in range(points, -1, -3):
        points_left = points - pennies
        dimes, dollars, gold = get_max_dimes(points_left)
        score = pennies * (1 + dimes * (1 + dollars * (1 + gold)))
        if score >= last_max_score:
            last_max_combo = (pennies, dimes, dollars, gold)
            last_max_score = score
        else:
            # It seems like this function has one global maximum
            # although I haven't proved this
            pass # break
    return last_max_combo, last_max_score

'''
Input: points is the number of points to spend on dimes, dollars, and gold combined
Output: returns a tuple containing 
    (dimes, dollars, gold)
'''
@cache
def get_max_dimes(points):
    # the cost of dimes, dollars, and gold are all divisible by 3
    # hence, the max number of coins with x points will be the same 
    # as the max number of coins with y points, where y is x rounded 
    # to the nearest multiple of 3
    point_remainder = points % 3
    if point_remainder != 0:
        return get_max_dimes(points - point_remainder)
    max_dimes = points // 3
    last_max_score = -1
    last_max_combo = (0, 0, 0)
    for dimes in range(max_dimes, -1, -1):
        points_left = points - dimes * 3
        dollars, gold = get_max_dollars(points_left)
        partial_score = dimes * (1 + dollars * (1 + gold))
        if partial_score >= last_max_score:
            last_max_combo = (dimes, dollars, gold)
            last_max_score = partial_score
    return last_max_combo

'''
Input: points is the number of points to spend on dollars and gold combined
Output: returns a tuple containing 
    (dollars, gold)
'''
@cache
def get_max_dollars(points):
    max_dollars = points // 6
    last_max_score = -1
    last_max_combo = (0, 0)
    for dollars in range(max_dollars, -1, -1):
        points_left = points - dollars * 6
        gold = points_left // 15
        partial_score = dollars * (1 + gold)
        if partial_score >= last_max_score:
            last_max_combo = (dollars, gold)
            last_max_score = partial_score
    return last_max_combo
    

while True:
    start_points = int(input("Start points: "))
    end_points = input("End points (leave blank for single value): ")
    if end_points == '':
        end_points = start_points
    end_points = int(end_points)
    if end_points < start_points:
        start_points, end_points = end_points, start_points
    
    print()
    
    time_start = time.perf_counter()
    
    for y in range(end_points, start_points - 1, -1):
        combo = get_max_score(y)
        print(f"score: {y}, {combo}")
    
    time_end = time.perf_counter()
    
    print(f"\nComputation took {time_end - time_start:.5f}s")
    if input("\nRun again? [y]/[n] ") != "y":
        break
    print()
$\endgroup$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.