From ea36c827283e928e616197b7789cb7e6bb06aca2 Mon Sep 17 00:00:00 2001 From: raj_mathe Date: Tue, 14 Jun 2022 01:35:10 +0200 Subject: [PATCH] =?UTF-8?q?master=20>=20master:=20code=20py=20-=20algorith?= =?UTF-8?q?men=20f=C3=BCr=20rucksackproblem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/algorithms/rucksack/__init__.py | 17 ++ .../src/algorithms/rucksack/algorithms.py | 205 ++++++++++++++++++ .../python/src/algorithms/rucksack/display.py | 62 ++++++ code/python/src/api.py | 2 + code/python/src/endpoints/__init__.py | 2 + .../src/endpoints/ep_algorithm_rucksack.py | 54 +++++ code/python/src/endpoints/ep_algorithm_tsp.py | 2 +- code/python/src/models/config/commands.py | 2 + code/python/src/models/rucksack/__init__.py | 20 ++ code/python/src/models/rucksack/mask.py | 80 +++++++ code/python/src/models/rucksack/solution.py | 43 ++++ code/python/src/models/stacks/stack.py | 10 + code/python/src/thirdparty/maths.py | 2 + 13 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 code/python/src/algorithms/rucksack/__init__.py create mode 100644 code/python/src/algorithms/rucksack/algorithms.py create mode 100644 code/python/src/algorithms/rucksack/display.py create mode 100644 code/python/src/endpoints/ep_algorithm_rucksack.py create mode 100644 code/python/src/models/rucksack/__init__.py create mode 100644 code/python/src/models/rucksack/mask.py create mode 100644 code/python/src/models/rucksack/solution.py diff --git a/code/python/src/algorithms/rucksack/__init__.py b/code/python/src/algorithms/rucksack/__init__.py new file mode 100644 index 0000000..f9729c1 --- /dev/null +++ b/code/python/src/algorithms/rucksack/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.algorithms.rucksack.algorithms import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'rucksack_greedy_algorithm', + 'rucksack_branch_and_bound_algorithm', +]; diff --git a/code/python/src/algorithms/rucksack/algorithms.py b/code/python/src/algorithms/rucksack/algorithms.py new file mode 100644 index 0000000..deb91aa --- /dev/null +++ b/code/python/src/algorithms/rucksack/algorithms.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.types import *; +from src.thirdparty.maths import *; + +from models.generated.config import *; +from src.models.rucksack import *; +from src.models.stacks import *; +from src.algorithms.rucksack.display import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'rucksack_greedy_algorithm', + 'rucksack_branch_and_bound_algorithm', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHOD greedy algorithm +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def rucksack_greedy_algorithm( + capacity: float, + weights: np.ndarray, + values: np.ndarray, + items: np.ndarray, + fractional: bool, + verbose: bool, +) -> Solution: + ''' + Durch den Greedy-Algorithm wird der optimale Wert eines Rucksacks + unter Rücksicht der Kapizitätsschranke eingeschätzt. + + NOTE: Wenn man `fractional = True` verwendet, liefert der Algorithmus + eine obere Schranke des maximalen Wertes beim Originalproblem. + ''' + # sortiere daten: + resort_by_value_per_weight(weights=weights, values=values, items=items); + + # führe greedy aus: + n = len(weights); + weight_total = 0; + vector = [ 0 for _ in range(n) ]; + rucksack = []; + for i in range(n): + # füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke + if weight_total + weights[i] <= capacity: + weight_total += weights[i]; + rucksack.append(i); + vector[i] = 1; + # sonst abbrechen. Falls Bruchteile erlaubt, füge einen Bruchteil des i. Items hinzu + else: + if fractional: + rucksack.append(i); + vector[i] = (capacity - weight_total)/weights[i]; + break; + + # verbose output hier behandeln (irrelevant für Algorithmus): + if verbose: + repr = display_greedy(vector=vector, values=values); + print(''); + print('\x1b[1mRucksack Problem - Greedy\x1b[0m'); + print(''); + print(repr); + print(''); + + # Lösung ausgeben + return Solution( + vector = vector, + items = items[rucksack].tolist(), + weights = weights[rucksack].tolist(), + values = values[rucksack].tolist(), + ); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHOD branch and bound algorithm +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def rucksack_branch_and_bound_algorithm( + capacity: float, + weights: np.ndarray, + values: np.ndarray, + items: np.ndarray, + verbose: bool, +) -> Solution: + ''' + Durch Branch & Bound wird der optimale Wert eines Rucksacks + unter Rücksicht der Kapizitätsschranke exakt und effizienter bestimmt. + ''' + + resort_by_value_per_weight(weights=weights, values=values, items=items); + logged_steps = []; + + vector = empty_mask(n=len(weights)); + lb_estimate = np.inf; + S = Stack(); + S.push(vector); + while not S.empty(): + lb, u = estimate_lower_bound(mask=S.top(), capacity=capacity, weights=weights, values=values, items=items); + if verbose: + logged_steps.append((lb_estimate, lb, u, str(S))); + # Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert: + A: Mask = S.pop(); + if lb < lb_estimate: + # Branch: + if A.splittable(): + B, C = A.split(); + # NOTE: per Wahl erfüllt mind. eine Möglichkeit in B die Kapazitätsschranke. + S.push(B); + # Nur dann C auf Stack legen, wenn mind. eine Möglichkeit in C die Kapazitätsschranke erfüllt: + if sum(weights[C.indexes_one]) <= capacity: + S.push(C); + # Bound, wenn die Maske den Rucksack komplett bestimmt: + else: + lb_estimate = lb; + vector = A; + + # verbose output hier behandeln (irrelevant für Algorithmus): + if verbose: + repr = display_branch_and_bound(values=values, steps=logged_steps); + print(''); + print('\x1b[1mRucksack Problem - Branch & Bound\x1b[0m'); + print(''); + print(repr); + print(''); + + # Lösung ausgeben + rucksack = vector.indexes_one; # Indexes von Items im Rucksack + return Solution( + vector = vector.decision, + items = items[rucksack].tolist(), + values = values[rucksack].tolist(), + weights = weights[rucksack].tolist(), + ); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# AUXILIARY METHOD resort +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def resort_by_value_per_weight( + weights: np.ndarray, + values: np.ndarray, + items: np.ndarray, +) -> List[int]: + ''' + Sortiert Daten absteigend nach values/weights. + ''' + n = len(weights); + indexes = list(range(n)); + order = sorted(indexes, key=lambda i: -values[i]/weights[i]); + weights[:] = weights[order]; + values[:] = values[order]; + items[:] = items[order]; + return order; + +def estimate_lower_bound( + mask: Mask, + capacity: float, + weights: np.ndarray, + values: np.ndarray, + items: np.ndarray, +) -> Tuple[float, List[float]]: + ''' + Wenn partielle Information über den Rucksack festgelegt ist, + kann man bei dem unbekannten Teil das Rucksack-Problem + mit Greedy-Algorithmus »lösen«, + um schnell eine gute Einschätzung zu bestimmen. + + NOTE: Diese Funktion wird `g(vector)` im Skript bezeichnet. + ''' + + # Berechnungen bei Items mit bekanntem Status in Rucksack: + indexes_one = mask.indexes_one; + weight_rucksack = sum(weights[indexes_one]); + value_rucksack = sum(values[indexes_one]); + + # Löse mit Greedy-Algorithmus auf Items mit unbekanntem Status. + # NOTE: Lösung ist eine Überschätzung des max-Wertes. + indexes_unset = mask.indexes_unset; + soln_rest = rucksack_greedy_algorithm( + capacity = capacity - weight_rucksack, # <- Kapazität = Restgewicht + weights = weights[indexes_unset], + values = values[indexes_unset], + items = items[indexes_unset], + fractional = True, + verbose = False, + ); + value_rest = soln_rest.total_value; + + # Lösung des [0,1]-Vektors speichern: + n = len(mask); + vector = np.zeros(shape=(n,), dtype=float); + vector[indexes_one] = 1; + vector[indexes_unset] = soln_rest.vector; + + # Einschätzung des max-Wertes (Ausgabe mit -1 multiplizieren): + value_max_est = value_rucksack + value_rest; + return -value_max_est, vector.tolist(); diff --git a/code/python/src/algorithms/rucksack/display.py b/code/python/src/algorithms/rucksack/display.py new file mode 100644 index 0000000..4291923 --- /dev/null +++ b/code/python/src/algorithms/rucksack/display.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.types import *; +from src.thirdparty.maths import *; + +from src.models.stacks import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'display_greedy', + 'display_branch_and_bound', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS display +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def display_greedy( + vector: Union[List[int], List[float]], + values: np.ndarray, +) -> str: + value = sum([ u*x for u, x in zip(vector,values)]); + expr = '+'.join([ + f'{x:g}' if u == 1 else f'{Fraction(str(u))}·{x:g}' + for u, x in zip(vector,values) if u > 0 + ]); + return f'{value:g} (={expr})'; + +def display_branch_and_bound( + values: np.ndarray, + steps: List[Tuple[float, float, Stack]] +) -> str: + # füge Summen-Ausdrücke für Greedy-Alg hinzu: + rows = []; + used_vectors = []; + for lb_estimate, lb, u, S in steps: + if u in used_vectors: + rows.append((f'{lb_estimate:g}', f'{lb:g}', S)); + else: + used_vectors.append(u) + rows.append((f'{lb_estimate:g}', display_greedy(vector=u, values=values), S)); + + table = pd.DataFrame(rows) \ + .rename(columns={0: 'b', 1: 'g(TOP(S))', 2: 'S'}) \ + .reset_index(drop=True); + # benutze pandas-Dataframe + tabulate, um schöner darzustellen: + repr = tabulate( + pd.DataFrame(table), + headers=['b', 'g(TOP(S))', 'S'], + showindex=False, + colalign=('left', 'left', 'right'), + tablefmt='rst' + ); + return repr; diff --git a/code/python/src/api.py b/code/python/src/api.py index 64d1920..28ad4e2 100644 --- a/code/python/src/api.py +++ b/code/python/src/api.py @@ -38,4 +38,6 @@ def run_command(command: Command) -> Result[CallResult, CallError]: return endpoint_tsp(command); elif isinstance(command, CommandHirschberg): return endpoint_hirschberg(command); + elif isinstance(command, CommandRucksack): + return endpoint_rucksack(command); raise Exception(f'No endpoint set for `{command.name.value}`-command type.'); diff --git a/code/python/src/endpoints/__init__.py b/code/python/src/endpoints/__init__.py index 4c93022..4ca27f3 100644 --- a/code/python/src/endpoints/__init__.py +++ b/code/python/src/endpoints/__init__.py @@ -8,6 +8,7 @@ from src.endpoints.ep_algorithm_hirschberg import *; from src.endpoints.ep_algorithm_tarjan import *; from src.endpoints.ep_algorithm_tsp import *; +from src.endpoints.ep_algorithm_rucksack import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -17,4 +18,5 @@ __all__ = [ 'endpoint_hirschberg', 'endpoint_tarjan', 'endpoint_tsp', + 'endpoint_rucksack', ]; diff --git a/code/python/src/endpoints/ep_algorithm_rucksack.py b/code/python/src/endpoints/ep_algorithm_rucksack.py new file mode 100644 index 0000000..35e4702 --- /dev/null +++ b/code/python/src/endpoints/ep_algorithm_rucksack.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; +from src.thirdparty.maths import *; + +from models.generated.commands import *; +from src.core.calls import *; +from src.setup import config; +from src.algorithms.rucksack import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'endpoint_rucksack', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENDPOINT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@run_safely() +def endpoint_rucksack(command: CommandRucksack) -> Result[CallResult, CallError]: + n = len(command.weights); + assert len(command.values) == n, f'Number of weights must be {n}'; + assert len(command.items) in [0, n], f'Number of items must be 0 or {n}'; + command.items = command.items or [ str(index + 1) for index in range(n) ]; + match command.algorithm: + case EnumRucksackAlgorithm.greedy: + result = rucksack_greedy_algorithm( + capacity = command.capacity, + weights = np.asarray(command.weights[:]), + values = np.asarray(command.values[:]), + items = np.asarray(command.items[:]), + fractional = command.allow_fractional, + verbose = config.OPTIONS.rucksack.verbose, + ); + case EnumRucksackAlgorithm.branch_and_bound: + result = rucksack_branch_and_bound_algorithm( + capacity = command.capacity, + weights = np.asarray(command.weights[:]), + values = np.asarray(command.values[:]), + items = np.asarray(command.items[:]), + verbose = config.OPTIONS.rucksack.verbose, + ); + case _ as alg: + raise Exception(f'No algorithm implemented for {alg.value}.') + return Ok(CallResult(action_taken=True, message=result)); diff --git a/code/python/src/endpoints/ep_algorithm_tsp.py b/code/python/src/endpoints/ep_algorithm_tsp.py index 7dd772a..6b0bd44 100644 --- a/code/python/src/endpoints/ep_algorithm_tsp.py +++ b/code/python/src/endpoints/ep_algorithm_tsp.py @@ -29,7 +29,7 @@ __all__ = [ def endpoint_tsp(command: CommandTsp) -> Result[CallResult, CallError]: result = tsp_algorithm( dist = np.asarray(command.dist, dtype=float), - optimise = min if command.optimise == EnumTSPOptimise.min else max, + optimise = min if command.optimise == EnumOptimiseMode.min else max, verbose = config.OPTIONS.tsp.verbose, ); return Ok(CallResult(action_taken=True, message=result)); diff --git a/code/python/src/models/config/commands.py b/code/python/src/models/config/commands.py index e4fd444..f5d6aef 100644 --- a/code/python/src/models/config/commands.py +++ b/code/python/src/models/config/commands.py @@ -42,4 +42,6 @@ def interpret_command(command: Command) -> Command: return CommandTsp(**command.dict()); case EnumAlgorithmNames.hirschberg: return CommandHirschberg(**command.dict()); + case EnumAlgorithmNames.rucksack: + return CommandRucksack(**command.dict()); raise command; diff --git a/code/python/src/models/rucksack/__init__.py b/code/python/src/models/rucksack/__init__.py new file mode 100644 index 0000000..f5395c7 --- /dev/null +++ b/code/python/src/models/rucksack/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.models.rucksack.mask import *; +from src.models.rucksack.solution import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'empty_mask', + 'MaskValue', + 'Mask', + 'Solution', +]; diff --git a/code/python/src/models/rucksack/mask.py b/code/python/src/models/rucksack/mask.py new file mode 100644 index 0000000..fc36d65 --- /dev/null +++ b/code/python/src/models/rucksack/mask.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations; + +from src.thirdparty.types import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'empty_mask', + 'MaskValue', + 'Mask', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENUMS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class MaskValue(Enum): + ZERO = 0; + ONE = 1; + UNSET = '*'; + +class Mask(): + index: int; + values: List[MaskValue]; + + def __init__(self, values: List[MaskValue]): + self.values = values; + if MaskValue.UNSET in values: + self.index = values.index(MaskValue.UNSET); + else: + self.index = -1; + return; + + def __len__(self) -> int: + return len(self.values); + + def __str__(self) -> str: + return ''.join([ str(m.value) for m in self.values ]); + + @property + def decision(self) -> List[int]: + return [ x.value for x in self.values ]; + + @property + def indexes_set(self) -> List[int]: + return [i for i, value in enumerate(self.values) if value != MaskValue.UNSET]; + + @property + def indexes_one(self) -> List[int]: + return [i for i, value in enumerate(self.values) if value == MaskValue.ONE]; + + @property + def indexes_zero(self) -> List[int]: + return [i for i, value in enumerate(self.values) if value == MaskValue.ZERO]; + + @property + def indexes_unset(self) -> List[int]: + return [i for i, value in enumerate(self.values) if value == MaskValue.UNSET]; + + def splittable(self) -> bool: + return self.index >= 0; + + def split(self) -> Tuple[Mask, Mask]: + vector1 = self.values[:]; + vector1[self.index] = MaskValue.ZERO; + vector2 = self.values[:]; + vector2[self.index] = MaskValue.ONE; + return Mask(vector1), Mask(vector2); + +def empty_mask(n: int): + return Mask([MaskValue.UNSET for _ in range(n)]); diff --git a/code/python/src/models/rucksack/solution.py b/code/python/src/models/rucksack/solution.py new file mode 100644 index 0000000..3679f01 --- /dev/null +++ b/code/python/src/models/rucksack/solution.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations +from dataclasses import asdict; + +from src.thirdparty.types import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'Solution', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENUMS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@dataclass +class SolutionRaw(): + vector: Union[List[int], List[float]] = field(); + items: List[str] = field(); + values: List[float] = field(repr=False); + weights: List[float] = field(repr=False); + +class Solution(SolutionRaw): + @property + def support(self) -> List[float]: + return [ i for i, v in enumerate(self.vector) if v > 0 ]; + + @property + def total_weight(self) -> float: + return sum([ self.vector[i]*x for (i, x) in zip(self.support, self.weights) ]); + + @property + def total_value(self) -> float: + return sum([ self.vector[i]*x for (i, x) in zip(self.support, self.values) ]); diff --git a/code/python/src/models/stacks/stack.py b/code/python/src/models/stacks/stack.py index 936479f..980eb56 100644 --- a/code/python/src/models/stacks/stack.py +++ b/code/python/src/models/stacks/stack.py @@ -39,6 +39,13 @@ class Stack: def __contains__(self, value: Any) -> bool: return value in self.elements; + def __iter__(self) -> Generator[Any, None, None]: + for value in self.elements: + yield value; + + def __str__(self) -> str: + return ', '.join([str(value) for value in self.elements[::-1]]); + def push(self, value: Any): ''' add element to stack @@ -68,3 +75,6 @@ class Stack: checks if element in stack: ''' return element in self.elements; + + def empty(self) -> bool: + return len(self) == 0; diff --git a/code/python/src/thirdparty/maths.py b/code/python/src/thirdparty/maths.py index ca01076..8bf9eef 100644 --- a/code/python/src/thirdparty/maths.py +++ b/code/python/src/thirdparty/maths.py @@ -5,6 +5,7 @@ # IMPORTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +from fractions import Fraction; import math; import numpy as np; import pandas as pd; @@ -16,6 +17,7 @@ from tabulate import tabulate; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'Fraction', 'math', 'np', 'pd',