diff --git a/README.md b/README.md index 1f5c67f..d2d1a05 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Fun with algorithms and data structures Have fun, learn about algorithms and data structures and build the fastest and cleanest algorithms from far west. - ## Local Development Though, it's not a requirement, we encourage the usage of `pyenv`, a simple Python version management program, to select the right Python version (>=3.8,<4.0). You can follow the [Windows](https://github.com/pyenv-win/pyenv-win#installation) or the [macOS instructions](https://github.com/pyenv/pyenv#installation), both found in the official `pyenv` repository. Installation on Linux based systems is not so straightforward but it is also doable. diff --git a/solutions/greedy/fractional_knapsack/item.py b/solutions/greedy/fractional_knapsack/item.py index 831c69e..a0275ac 100644 --- a/solutions/greedy/fractional_knapsack/item.py +++ b/solutions/greedy/fractional_knapsack/item.py @@ -1,15 +1,11 @@ -from dataclasses import dataclass from typing import List -@dataclass class Item: - value: float - weight: int - - @property - def fractional_value(self): - return self.value / self.weight + def __init__(self, value: float, weight: int) -> None: + self.value: float + self.weight: int + self.fractional_value: float = value / weight def __gt__(self, other): if not self._is_valid_operand(other): diff --git a/solutions/greedy/minimum_refueling_stops/__init__.py b/solutions/greedy/minimum_refueling_stops/__init__.py new file mode 100644 index 0000000..bcd656b --- /dev/null +++ b/solutions/greedy/minimum_refueling_stops/__init__.py @@ -0,0 +1,11 @@ +from solutions.greedy.minimum_refueling_stops.brute_force_solution import ( + brute_force_solution, +) +from solutions.greedy.minimum_refueling_stops.efficient_solution import ( + efficient_solution, +) + +__all__ = [ + "brute_force_solution", + "efficient_solution", +] diff --git a/solutions/greedy/minimum_refueling_stops/brute_force_solution.py b/solutions/greedy/minimum_refueling_stops/brute_force_solution.py new file mode 100644 index 0000000..debbf6f --- /dev/null +++ b/solutions/greedy/minimum_refueling_stops/brute_force_solution.py @@ -0,0 +1,32 @@ +from typing import List, Tuple + + +def brute_force_solution( + input_min_refueling_stops: Tuple[float, float, List[float]] +) -> int: + """Solve the problem using brute force. The brute force solution is consists on a + nested loop, hence it is O(n^2). + + Parameters + ---------- + input_min_refueling_stops : Tuple[float, float, List[int]] + The input data, a tuple of the form (distance, tank_capacity, stations). + + Returns + ------- + int + The minimum number of refueling stops. + """ + destination_distance, tank_capacity, gas_stations = input_min_refueling_stops + stops = [0.0] + gas_stations + [destination_distance] + refuel = 0 + i, j = 0, 0 + while i + 2 < len(stops): + i += j + for j, next_gas_station in enumerate(stops[(i + 1) :]): + if tank_capacity < next_gas_station - stops[i]: + if j == 0: + return -1 + refuel += 1 + break + return refuel diff --git a/solutions/greedy/minimum_refueling_stops/efficient_solution.py b/solutions/greedy/minimum_refueling_stops/efficient_solution.py new file mode 100644 index 0000000..88f71c9 --- /dev/null +++ b/solutions/greedy/minimum_refueling_stops/efficient_solution.py @@ -0,0 +1,33 @@ +from typing import List, Tuple + + +def efficient_solution( + input_min_refueling_stops: Tuple[float, float, List[float]] +) -> int: + """Solve the problem using a greedy solution. The greedy solution is consists on + refueling at the last possible station that the tank deposit capacity allows, then + removing the previous stations and start again. This is O(1) in time and O(n) in + space. + + Parameters + ---------- + input_min_refueling_stops : Tuple[float, float, List[float]] + The input data, a tuple of the form (distance, tank_capacity, stations). + + Returns + ------- + int + The minimum number of refueling stops. + """ + destination_distance, tank_capacity, gas_stations = input_min_refueling_stops + stops = [0.0] + gas_stations + [destination_distance] + refueling, i = 0, 0 + while i < len(stops) - 1: + i += 1 + if tank_capacity < stops[i] - stops[0]: + if i == 1: + return -1 + refueling += 1 + stops = stops[i - 1 :] + i = 0 + return refueling diff --git a/tests/config.yaml b/tests/config.yaml index 62f7783..4a3cc1d 100644 --- a/tests/config.yaml +++ b/tests/config.yaml @@ -228,3 +228,39 @@ fractional_knapsack: repeat_performance_tests_per_input: 3 number_performance_tests_per_input: 5 times_faster_efficient_to_brute_force: 1000 +minimum_refueling_stops: + stress_tests: + small_tests: + max_fuel_stops: 100 + min_fuel_stops: 2 + max_distance_between_stops: 50 + min_distance_between_stops: 10 + max_tank_capacity: 100 + min_tank_capacity: 20 + max_distance_to_destination_from_last_stop: 100 + min_distance_to_destination_from_last_stop: 50 + test_iterations: 10 + big_tests: + max_fuel_stops: 1000 + min_fuel_stops: 200 + max_distance_between_stops: 50 + min_distance_between_stops: 10 + max_tank_capacity: 100 + min_tank_capacity: 20 + max_distance_to_destination_from_last_stop: 100 + min_distance_to_destination_from_last_stop: 50 + test_iterations: 100 + performance: + max_fuel_stops: 250000 + min_fuel_stops: 240000 + max_distance_between_stops: 50 + min_distance_between_stops: 10 + max_tank_capacity: 100 + min_tank_capacity: 20 + max_distance_to_destination_from_last_stop: 100 + min_distance_to_destination_from_last_stop: 50 + test_iterations: 1 + performance_tests: + repeat_performance_tests_per_input: 2 + number_performance_tests_per_input: 3 + times_faster_efficient_to_brute_force: 1.05 diff --git a/tests/greedy/fractional_knapsack/test_efficient_solution.py b/tests/greedy/fractional_knapsack/test_efficient_solution.py index 3c277c6..a4bf5e5 100644 --- a/tests/greedy/fractional_knapsack/test_efficient_solution.py +++ b/tests/greedy/fractional_knapsack/test_efficient_solution.py @@ -70,7 +70,9 @@ def sample_input_fractional_knapsack( @pytest.mark.parametrize( "input_fractional_knapsack", sample_input_fractional_knapsack(small_tests_params) ) -def test_stress_tests_efficient_solution_small_stress(input_fractional_knapsack): +def test_stress_tests_efficient_solution_small_stress( + input_fractional_knapsack: Tuple[int, int, List[Item]] +) -> None: input_fractional_knapsack_copy = copy.deepcopy(input_fractional_knapsack) brute_force = fractional_knapsack.brute_force_solution( input_fractional_knapsack_copy @@ -83,7 +85,9 @@ def test_stress_tests_efficient_solution_small_stress(input_fractional_knapsack) "input_fractional_knapsack", sample_input_fractional_knapsack(big_tests_params), ) -def test_stress_tests_efficient_solution_big_stress(input_fractional_knapsack): +def test_stress_tests_efficient_solution_big_stress( + input_fractional_knapsack: Tuple[int, int, List[Item]] +) -> None: input_fractional_knapsack_copy = copy.deepcopy(input_fractional_knapsack) efficient = fractional_knapsack.efficient_solution(input_fractional_knapsack) brute_force = fractional_knapsack.brute_force_solution( @@ -100,7 +104,7 @@ def test_stress_tests_efficient_solution_big_stress(input_fractional_knapsack): @pytest.mark.parametrize( "input_fractional_knapsack", sample_input_fractional_knapsack(performance_params) ) -def test_performance(input_fractional_knapsack) -> None: +def test_performance(input_fractional_knapsack: Tuple[int, int, List[Item]]) -> None: config_names = ["fractional_knapsack", "performance_tests"] performance_measures = read_config(PerformanceMeasures, config_names) input_fractional_knapsack_copy = copy.deepcopy(input_fractional_knapsack) @@ -113,5 +117,4 @@ def test_performance(input_fractional_knapsack) -> None: input_fractional_knapsack, ) times_faster = performance_measures.times_faster_efficient_to_brute_force - assert times_faster * efficient_time < brute_force_time diff --git a/tests/greedy/minimum_refueling_stops/__init__.py b/tests/greedy/minimum_refueling_stops/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/greedy/minimum_refueling_stops/stress_tests_params.py b/tests/greedy/minimum_refueling_stops/stress_tests_params.py new file mode 100644 index 0000000..87d68df --- /dev/null +++ b/tests/greedy/minimum_refueling_stops/stress_tests_params.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass + +from tests.read_config import Config + + +@dataclass +class StressTestsParams(Config): + max_fuel_stops: int + min_fuel_stops: int + max_distance_between_stops: int + min_distance_between_stops: int + max_tank_capacity: int + min_tank_capacity: int + max_distance_to_destination_from_last_stop: int + min_distance_to_destination_from_last_stop: int + test_iterations: int + seed: int = 123 diff --git a/tests/greedy/minimum_refueling_stops/test_brute_force_solution.py b/tests/greedy/minimum_refueling_stops/test_brute_force_solution.py new file mode 100644 index 0000000..39a8b2c --- /dev/null +++ b/tests/greedy/minimum_refueling_stops/test_brute_force_solution.py @@ -0,0 +1,10 @@ +from solutions.greedy import minimum_refueling_stops + + +def test_brute_force_solution(): + assert 2 == minimum_refueling_stops.brute_force_solution( + (950, 400, [200, 375, 550, 750]) + ) + + assert -1 == minimum_refueling_stops.brute_force_solution((10, 3, [1, 2, 5, 9])) + assert 0 == minimum_refueling_stops.brute_force_solution((200, 250, [100, 150])) diff --git a/tests/greedy/minimum_refueling_stops/test_efficient_solution.py b/tests/greedy/minimum_refueling_stops/test_efficient_solution.py new file mode 100644 index 0000000..8fbe021 --- /dev/null +++ b/tests/greedy/minimum_refueling_stops/test_efficient_solution.py @@ -0,0 +1,130 @@ +import random +from typing import List, Tuple + +import pytest + +from solutions.greedy import minimum_refueling_stops +from tests.greedy.minimum_refueling_stops.stress_tests_params import StressTestsParams +from tests.performance_measures import PerformanceMeasures +from tests.read_config import get_names, read_config + + +def test_efficient_solution_simple_cases() -> None: + assert 2 == minimum_refueling_stops.efficient_solution( + (950, 400, [200, 375, 550, 750]) + ) + + assert -1 == minimum_refueling_stops.efficient_solution((10, 3, [1, 2, 5, 9])) + assert 0 == minimum_refueling_stops.efficient_solution((200, 250, [100, 150])) + + +# +# get test parameters +# + + +names = ["minimum_refueling_stops", "stress_tests"] +small_tests_params = read_config(StressTestsParams, get_names(names, "small_tests")) +big_tests_params = read_config(StressTestsParams, get_names(names, "big_tests")) +performance_params = read_config(StressTestsParams, get_names(names, "performance")) + +# +# parametrize test inputs +# + + +def _cumsum(x: List[int]) -> List[int]: + res = [] + elements_sum = 0 + for element in x: + elements_sum += element + res.append(elements_sum) + return res + + +def sample_input_minimum_refueling_stops( + params: StressTestsParams, +) -> List[Tuple[int, int, List[int]]]: + random.seed(params.seed) + test_cases = [] + for _ in range(params.test_iterations): + number_stops = random.randint(params.min_fuel_stops, params.max_fuel_stops) + min_distance = params.min_distance_between_stops + max_distance = params.max_distance_between_stops + stops = _cumsum( + [random.randint(min_distance, max_distance) for _ in range(number_stops)] + ) + distance_to_destination = ( + random.randint( + params.min_distance_to_destination_from_last_stop, + params.max_distance_to_destination_from_last_stop, + ) + + stops[-1] + ) + tank_capacity = random.randint( + params.min_tank_capacity, params.max_tank_capacity + ) + test_cases.append((distance_to_destination, tank_capacity, stops)) + return test_cases + + +# +# stress tests +# + + +@pytest.mark.parametrize( + "input_minimum_refueling_stops", + sample_input_minimum_refueling_stops(small_tests_params), +) +def test_stress_tests_efficient_solution_small_stress( + input_minimum_refueling_stops: Tuple[float, float, List[float]] +) -> None: + brute_force = minimum_refueling_stops.brute_force_solution( + input_minimum_refueling_stops + ) + efficient = minimum_refueling_stops.efficient_solution( + input_minimum_refueling_stops + ) + assert brute_force == efficient + + +@pytest.mark.parametrize( + "input_minimum_refueling_stops", + sample_input_minimum_refueling_stops(big_tests_params), +) +def test_stress_tests_efficient_solution_big_stress( + input_minimum_refueling_stops: Tuple[float, float, List[float]] +) -> None: + brute_force = minimum_refueling_stops.brute_force_solution( + input_minimum_refueling_stops + ) + efficient = minimum_refueling_stops.efficient_solution( + input_minimum_refueling_stops + ) + assert brute_force == efficient + + +# +# performance tests +# + + +@pytest.mark.parametrize( + "input_minimum_refueling_stops", + sample_input_minimum_refueling_stops(performance_params), +) +def test_performance(input_minimum_refueling_stops: Tuple[int, int, List[int]]) -> None: + config_names = ["minimum_refueling_stops", "performance_tests"] + performance_measures = read_config(PerformanceMeasures, config_names) + brute_force_time = performance_measures.measure_performance( + minimum_refueling_stops.brute_force_solution, + input_minimum_refueling_stops, + ) + efficient_time = performance_measures.measure_performance( + minimum_refueling_stops.efficient_solution, + input_minimum_refueling_stops, + ) + + times_faster = performance_measures.times_faster_efficient_to_brute_force + assert times_faster * efficient_time < brute_force_time