Exemplo n.º 1
0
def cleanup_generate(
    left: int, right: int
) -> Dict[Tuple[int, int], Optional[Tuple[BaseAction, Target, Tuple[int,
                                                                    int]]]]:
    """Used to make mypy happier"""
    return {(int(key[0]), int(key[1])):
            ((value[0].base, value[0].target,
              (int(value[1][0]), int(value[1][1]))) if value else None)
            for key, value in generate_graph(BucketValueType(left),
                                             BucketValueType(right)).items()}
Exemplo n.º 2
0
def _wrap_performer(
        left: int,
        right: int
    ) -> Callable[[Action, Tuple[int, int]], BucketFilledState]:
    """
    This function wraps the existing behavior so that it passes mypy.
    It is designed to be a drop in place replacement for get_performer
    """
    performer = get_performer(BucketValueType(left), BucketValueType(right))
    def wrapper(action: Action, input_state: Tuple[int, int]) -> BucketFilledState:
        return performer(action, (BucketValueType(input_state[0]), BucketValueType(input_state[1])))
    return wrapper
Exemplo n.º 3
0
    def perform(action: Action,
                initial_state: BucketFilledState) -> BucketFilledState:
        """Performs the action on the specified bucket state (with the closed over bucket sizes)"""
        # I don't want to duplicate logic so:
        # Let's assume left
        max_fill = max_left
        # But if not, make adjustments
        if action.target == Target.Right:
            initial_state = initial_state[::-1]
            max_fill = max_right
        # which we'll reverse later

        transfer_would_overflow = (initial_state[0] + initial_state[1] >
                                   max_fill)
        typed_zero = BucketValueType(0)
        actions_performed = {
            BaseAction.Fill: (max_fill, initial_state[1]),
            BaseAction.Empty: (typed_zero, initial_state[1]),
            BaseAction.Transfer:
            (max_fill if transfer_would_overflow else initial_state[0] +
             initial_state[1], initial_state[1] - (max_fill - initial_state[0])
             if transfer_would_overflow else typed_zero)
        }
        final_state = actions_performed[action.base]

        # Fix if necessary
        if action.target == Target.Right:
            final_state = final_state[::-1]

        return final_state
Exemplo n.º 4
0
def _solve(
        *args: Union[float, int]
    ) -> Tuple[List[Tuple[BaseAction, Target, BucketFilledState]], Target]:
    """This is a wrapper around the solve function, which does two things:
    1. Returns a simpler set of objects for return type comparison
    2. Accepts ints for easier mypy type checking
    Both of these things are optional, but it makes writing and updating the tests better
    """
    return to_tuples(*solver.solve(*[BucketValueType(arg) for arg in args]))
Exemplo n.º 5
0
def main(*args_tuple: str) -> int:
    """The main console application, as a testable function"""
    args = list(args_tuple)
    graph_option = '--graph'
    requested_graph = graph_option in args
    if requested_graph:
        args.remove(graph_option)

    if len(args) in [2, 3]:
        try:
            args_to_pass = [BucketValueType(arg) for arg in args]
        except (ValueError,
                decimal.InvalidOperation):  # support both conversion errors
            return usage("Arguments must be numbers")

        if any(arg < 0 for arg in args_to_pass[:2]
               ):  # The first two arguments must not be negative
            return usage("Bucket sizes must be positive")

        if requested_graph:
            target = args_to_pass[2] if len(
                args_to_pass) > 2 else BucketValueType(-1)
            graph = solver.generate_graph(args_to_pass[0], args_to_pass[1])
            result = dot.to_dot(graph, target)
            print(result)
            return 0  # success

        try:
            actions, location = solver.solve(*args_to_pass)

            print("Steps:")
            for index, (action, state) in enumerate(actions):
                print(
                    f"{index + 1}. {action} (bucket state will be {state[0]}, {state[1]})"
                )
            print(f"The target amount will be in the {location.name} bucket")
        except solver.UnsolvableError as ex:
            print("No Solution")
            print(f"Perhaps try one of these:", *sorted(ex.valid))
    else:
        return usage("Incorrect number of arguments")

    return 0  # 0 is success code for command line
Exemplo n.º 6
0
def generate_graph(
    max_left: BucketValueType,
    max_right: BucketValueType,
    break_out_early: Callable[[BucketFilledState], bool] = lambda _: False
) -> Graph:
    """Generates a whole graph"""

    # A queue allows us to do a breadth-first traversal, which will guarantee "optimal" solutions
    actions_to_consider: Deque[Tuple[Action,
                                     BucketFilledState]] = collections.deque()
    graph: Graph = {}

    def put_all(current: BucketFilledState) -> None:
        for action in ALL_ACTIONS:
            actions_to_consider.appendleft((action, current))

    perform = get_performer(max_left, max_right)

    basis_case = (BucketValueType(0), BucketValueType(0))

    graph[basis_case] = None  # Base case
    if break_out_early(basis_case):
        return graph

    put_all(basis_case)  # Starting condition is both are empty

    while actions_to_consider:
        action_considering, parent = actions_to_consider.pop()
        result_of_action = perform(action_considering, parent)

        if result_of_action not in graph:
            graph[result_of_action] = (action_considering, parent)

            if break_out_early(result_of_action):
                return graph

            put_all(result_of_action)

    return graph
Exemplo n.º 7
0
def solve(
    max_left: BucketValueType,
    max_right: BucketValueType,
    target_amount: Optional[BucketValueType] = BucketValueType(-1)
) -> Tuple[List[Tuple[Action, BucketFilledState]], Target]:
    """Returns the list of actions, and a bucket which contains the correct amount"""

    # Variables which save the state for later calling
    found_state: Optional[BucketFilledState] = None
    direction: Optional[Target] = None

    def break_out_early(current: BucketFilledState) -> bool:
        """returns the target if it's found, otherwise None"""
        nonlocal found_state, direction
        if current[0] == target_amount:
            found_state = current
            direction = Target.Left
            return True
        if current[1] == target_amount:
            found_state = current
            direction = Target.Right
            return True
        return False

    graph = generate_graph(max_left, max_right, break_out_early)

    if found_state and direction:
        current_bucket_state = found_state
        path: List[Tuple[Action, BucketFilledState]] = []
        while True:
            node = graph[current_bucket_state]
            if node is None:
                return path, direction
            action, next_bucket_state = node
            path.insert(0, (action, current_bucket_state))
            current_bucket_state = next_bucket_state

    # couldn't find a solution, exhaustively find all results
    valid = set(values for left, right in graph for values in [left, right])
    raise UnsolvableError(valid)
Exemplo n.º 8
0
 def wrapper(action: Action, input_state: Tuple[int, int]) -> BucketFilledState:
     return performer(action, (BucketValueType(input_state[0]), BucketValueType(input_state[1])))
Exemplo n.º 9
0
def test_011() -> None:
    assert to_dot(generate_graph(BucketValueType(0), BucketValueType(1)),
                  BucketValueType(1)) == """
Exemplo n.º 10
0
def to_dot(graph: Graph, target: BucketValueType = BucketValueType(-1)) -> str:
    """Converts a bucket graph into a DOT graph"""

    return f"""
Exemplo n.º 11
0
def _state_row(
    state: BucketFilledState, target: BucketValueType = BucketValueType(-1)
) -> str:
    return f'{_name(state)} [label="{_state_label(state)}"]{HIGHLIGHT if target in state else ""}'