HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Pick what maintenance check required for game

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
checkwhatmaintenancegameforpickrequired

Problem

I have a Python script that plays Airline Manager 2 (on Facebook) for me. So far it can depart flights and buy fuel when the price is good. I would like the program to also automatically plan maintenance for aircraft. The following code will only be run when a check is due (one of the four checks will have the value 0.)

When an A-Check is due (hours till A-Check == 0,) the A-Check must be performed. Same goes for other checks. The twist is this: B-Checks also cover A, C covers A & B, and a D-Check covers all other checks. If an A-Check is required, I would like a B-Check to be performed if the B would run out before the A is needed again. There is no point in doing a B-Check if 2 hours later a C-Check would need to be done. In that case I might as well just do a C-Check (which also covers the B-Check.)

Examples

  • A-Check required; B-Check has 600 hours before another check is required; perform an A-Check.



  • The above sentence can be written as A,B,C,D=0,600,2000,10000 -> A-Check (C & D-Checks are set to their maximum values so they don't affect the result.)



  • A-Check required; B-Check has 200 hours left; perform a B-Check (because the B-Check would need to be done in another 200 hours anyway, so the A-Check would be covered now, then in another 200 hours with the B-Check, wasting 300 hours of A-Check.)



  • The above sentence can be written as A,B,C,D=0,200,2000,10000 -> B-Check (C & D-Checks are set to their maximum values so they don't affect the result.)



  • A,B,C,D=200,0,1300,9500 -> B-Check, which also covers the A-Check



  • A,B,C,D=200,0,700,9500 -> C-Check, because the C-Check would need to be done before the next B-Check (if a B-Check was performed.)



Rules

  • A-Checks should be performed every 500 hours



  • B-Checks should be performed every 1,000 hours and also covers A-Checks



  • C-Checks should be performed every 2,000 hours and also covers both A-Checks and B-Checks



  • D-Checks should be performed every 10,000 hours and covers all other checks as

Solution

You can get rid of your duplications by using loops. This comes without any performance hit in this case. Again, the limits have to be defined outside of the function, but this function can deal with a variable number of arguments, just like the numpy solution below.

limits = amax, bmax, cmax, dmax

def get_check_loop(*args):
    # assert len(limits) == len(args)
    for x, limit in zip(args, limits):
        if x == 0:
            i = len(args) - 1
            for t in reversed(args):
                if t - limit < 0:
                    return i
                i -= 1
    return -1


I had to use the slightly ugly manual decreasing of the inner loop variable, because enumerate is not an iterable, at least not as understood by reversed.

Ok, I found a numpy solution for this. It is slower than your approach, but shorter. Specifically, it is about 50 times slower (~10\$\mu\$s instead of ~200ns per call, on my machine).

First, I took your code and put it into a function. I left the max values outside of it. (This makes it slightly slower, because looking up global variables is slower. On the other hand, re-defining variables every call is also costly. In the end the two seem to cancel out.) I also changed the code to return numeric values (because that is easier for the numpy code I wrote). You can always use it as an index into the list ["a", "b", "c", "d", None], where the last entry is for the case that none of the values is zero, which I also included:

amax = 500
bmax = 1000
cmax = 2000
dmax = 10000

def get_check(a, b, c, d):
    if a == 0:
        if d - amax < 0:
            return 3
        elif c - amax < 0:
            return 2
        elif b - amax < 0:
            return 1
        else:
            return 0
    elif b == 0:
        if d - bmax < 0:
            return 3
        elif c - bmax < 0:
            return 2
        else:
            return 1
    elif c == 0:
        if d - cmax < 0:
            return 3
        else:
            return 2
    elif d == 0:
        return 3
    else:
        return -1


For the numpy approach we need to realize that if one of the variables is zero, then we need to subtract that variables max interval from all arguments. For example, if x = a, b, c, d = 351, 0, 304, 1500, then we need to have a look at x - bmax < 0 and choose the index of the last one that is below zero.

In code this is:

import numpy as np

bounds = np.array([500, 1000, 2000, 10000])

def argmax_last(x):
    """
    Returns the last occurrence of the maximum value in x.

    From http://stackoverflow.com/a/8768734/4042267
    """
    return len(x) - np.argmax(x[::-1]) - 1

def get_check_np(*v):
    if any(x == 0 for x in v):
        return argmax_last(v - bounds[np.argmin(v)] < 0)
    return -1


np.argmax (np.argmin) returns the index of the first maximum (minimum) in the passed array. argmax_last returns the last index where the value is the maximal value in that array. Examples:

>>> max([4, 1, 2, 4, 3, 2])
4
>>> np.argmax([4, 1, 2, 4, 3, 2])
0
>>> argmax_last([4, 1, 2, 4, 3, 2])
3


Note that the numpy function is more easily extendable (it only cares that bounds is at least as long as the position of the zero in the input arguments).

Code Snippets

limits = amax, bmax, cmax, dmax


def get_check_loop(*args):
    # assert len(limits) == len(args)
    for x, limit in zip(args, limits):
        if x == 0:
            i = len(args) - 1
            for t in reversed(args):
                if t - limit < 0:
                    return i
                i -= 1
    return -1
amax = 500
bmax = 1000
cmax = 2000
dmax = 10000


def get_check(a, b, c, d):
    if a == 0:
        if d - amax < 0:
            return 3
        elif c - amax < 0:
            return 2
        elif b - amax < 0:
            return 1
        else:
            return 0
    elif b == 0:
        if d - bmax < 0:
            return 3
        elif c - bmax < 0:
            return 2
        else:
            return 1
    elif c == 0:
        if d - cmax < 0:
            return 3
        else:
            return 2
    elif d == 0:
        return 3
    else:
        return -1
import numpy as np

bounds = np.array([500, 1000, 2000, 10000])


def argmax_last(x):
    """
    Returns the last occurrence of the maximum value in x.

    From http://stackoverflow.com/a/8768734/4042267
    """
    return len(x) - np.argmax(x[::-1]) - 1


def get_check_np(*v):
    if any(x == 0 for x in v):
        return argmax_last(v - bounds[np.argmin(v)] < 0)
    return -1
>>> max([4, 1, 2, 4, 3, 2])
4
>>> np.argmax([4, 1, 2, 4, 3, 2])
0
>>> argmax_last([4, 1, 2, 4, 3, 2])
3

Context

StackExchange Code Review Q#158558, answer score: 2

Revisions (0)

No revisions yet.