master > master: code py - models + config implementiert

This commit is contained in:
RD 2022-06-10 11:50:59 +02:00
parent 67f6caf2d5
commit 0523c68100
21 changed files with 538 additions and 94 deletions

View File

@ -22,12 +22,16 @@
!/src/**/*.py !/src/**/*.py
!/main.py !/main.py
!/models
!/models/*-schema.yaml
!/models/README.md
!/tests !/tests
!/tests/**/ !/tests/**/
!/tests/**/*.py !/tests/**/*.py
!/assets !/assets
!/assets/**/ !/assets/*.yaml
!/dist !/dist
!/dist/VERSION !/dist/VERSION

View File

@ -12,7 +12,7 @@ die Methoden mit Daten ausprobieren.
1. Der Python-Compiler **`^3.10.*`** wird benötigt. 1. Der Python-Compiler **`^3.10.*`** wird benötigt.
2. Es ist auch empfehlenswert, **`justfile`** zu installieren (siehe <https://github.com/casey/just#installation>). 2. Es ist auch empfehlenswert, **`justfile`** zu installieren (siehe <https://github.com/casey/just#installation>).
## Setup -> Test -> Run ## ## Build -> Test -> Run ##
In einem IDE in dem Repo zu diesem Ordner navigieren. In einem IDE in dem Repo zu diesem Ordner navigieren.
</br> </br>
@ -23,7 +23,7 @@ Wer das **justfile**-Tool hat:
# Zeige alle Befehle: # Zeige alle Befehle:
just just
# Zur Installation der Requirements (nur nach Änderungen): # Zur Installation der Requirements (nur nach Änderungen):
just setup; just build;
# Zur Ausführung der unit tests: # Zur Ausführung der unit tests:
just tests; just tests;
# Zur Ausführung des Programms # Zur Ausführung des Programms
@ -43,3 +43,9 @@ python3 main.py
Auf Windows verwendet man `py -3` od. `py -310` statt `python3`. Auf Windows verwendet man `py -3` od. `py -310` statt `python3`.
Man kann auch mit einem guten Editor/IDE die Tests einzeln ausführen. Man kann auch mit einem guten Editor/IDE die Tests einzeln ausführen.
## Testfälle durch Config-Datei ##
Statt den Code immer anfassen zu müssen, kann man Fälle für die verschiedenen Algorithmen
in der **[./assets/commands](assets/commands.yaml)** erfassen und (mittels `just run`)
das Programm ausführen.

View File

@ -0,0 +1,33 @@
## Beispiel für Seminarwoche 9 (Blatt 8)
- name: TSP
dist:
- [0, 7, 4, 3]
- [7, 0, 5, 6]
- [2, 5, 0, 5]
- [2, 7, 4, 0]
optimise: MIN
verbose: true
## Beispiele für Seminarwoche 10 (Blatt 9)
- &command_hirschberg
name: HIRSCHBERG
once: true
verbose:
- COSTS
- MOVES
# show:
# # - ATOMS
# - TREE
word1: 'happily ever after'
word2: 'apples'
- <<: *command_hirschberg
word1: 'ANSTRENGEN'
word2: 'ANSPANNEN'
- <<: *command_hirschberg
word1: 'ACGAAG'
word2: 'AGAT'
- <<: *command_hirschberg
word1: 'happily ever, lol'
word2: 'apple'
- <<: *command_hirschberg
word1: 'happily'
word2: 'applses'

View File

@ -0,0 +1,8 @@
info:
author: Raj Dahya
title: Algorithmen und Datenstrukturen 2
description: |-
Ein Code-Projekt, das Algorithmen und Datenstrukturen aus dem Kurs
ADS2 an der Universität Leipzig (Sommersemester 2022)
implementiert.
options: {}

View File

@ -1 +1 @@
0.0.0 0.1.0

View File

@ -11,6 +11,7 @@ _default:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PYTHON := if os_family() == "windows" { "py -3" } else { "python3" } PYTHON := if os_family() == "windows" { "py -3" } else { "python3" }
GEN_MODELS := "datamodel-codegen"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Macros # Macros
@ -42,6 +43,19 @@ _docker-build-and-log service:
_docker-build-and-interact service container: _docker-build-and-interact service container:
@docker compose up --build -d {{service}} && docker attach {{container}} @docker compose up --build -d {{service}} && docker attach {{container}}
_generate-models path name:
@{{GEN_MODELS}} \
--input-file-type openapi \
--encoding "UTF-8" \
--disable-timestamp \
--use-schema-description \
--allow-population-by-field-name \
--snake-case-field \
--strict-nullable \
--target-python-version 3.9 \
--input {{path}}/{{name}}-schema.yaml \
--output {{path}}/generated/{{name}}.py
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS # TARGETS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -50,8 +64,16 @@ _docker-build-and-interact service container:
# TARGETS: build # TARGETS: build
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build: build: _build-requirements _build-skip-requirements
_build-skip-requirements: build-models
_build-requirements:
@{{PYTHON}} -m pip install --disable-pip-version-check -r requirements.txt @{{PYTHON}} -m pip install --disable-pip-version-check -r requirements.txt
build-models: _check-system-requirements _build-models-nochecks
_build-models-nochecks:
@echo "Generate data models from schemata."
@just _create-folder-if-not-exists "models/generated"
@- just _generate-models "models" "config"
@- just _generate-models "models" "commands"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TARGETS: run # TARGETS: run
@ -145,3 +167,8 @@ _display-logs:
check-system: check-system:
@echo "Operating System detected: {{os_family()}}." @echo "Operating System detected: {{os_family()}}."
@echo "Python command used: {{PYTHON}}." @echo "Python command used: {{PYTHON}}."
_check-system-requirements:
@if ! ( {{GEN_MODELS}} --help >> /dev/null 2> /dev/null ); then \
echo "Command '{{GEN_MODELS}}' did not work. Ensure that the installation of 'datamodel-code-generator' worked and that system paths are set." \
exit 1; \
fi

View File

@ -6,18 +6,23 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import os; import os;
import sys; import sys
from token import MINUS;
os.chdir(os.path.join(os.path.dirname(__file__), '..')); os.chdir(os.path.join(os.path.dirname(__file__)));
sys.path.insert(0, os.getcwd()); sys.path.insert(0, os.getcwd());
from src.core.log import *;
from src.thirdparty.maths import *; from src.thirdparty.maths import *;
from models.generated.commands import *;
from src.core.log import *;
from src.setup.config import *;
from src.graphs.graph import *; from src.graphs.graph import *;
from src.graphs.tarjan import *; from src.graphs.tarjan import *;
from src.travel.naive import *; from src.tsp import *;
from src.hirschberg import *; from src.hirschberg import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# GLOBAL CONSTANTS/VARIABLES # GLOBAL CONSTANTS/VARIABLES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -29,38 +34,21 @@ from src.hirschberg import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def enter(): def enter():
# ## Beispiel für Seminarwoche 9 (Blatt 8): for command in COMMANDS:
# tsp_naive_algorithm( if isinstance(command, CommandTsp):
# dist = np.asarray([ tsp_algorithm(
# [0, 7, 4, 3], dist = np.asarray(command.dist, dtype=float),
# [7, 0, 5, 6], optimise = min if command.optimise == EnumTspOptimise.min else max,
# [2, 5, 0, 5], verbose = command.verbose,
# [2, 7, 4, 0], );
# ], dtype=float), elif isinstance(command, CommandHirschberg):
# optimise=min, hirschberg_algorithm(
# verbose=True, X = command.word1,
# ); Y = command.word2,
## Beispiel für Seminarwoche 10 (Blatt 9): once = command.once,
hirschberg_algorithm( verb = command.verbose,
# Y = 'ANSPANNEN', show = command.show,
# X = 'ANSTRENGEN', );
# Y = 'AGAT',
# X = 'ACGAAG',
Y = 'apples',
X = 'happily ever after',
# Y = 'applses',
# X = 'happily ever, lol',
# Y = 'apple',
# X = 'happily',
# once = True,
# verb = VerboseMode.COSTS,
# verb = VerboseMode.MOVES,
verb = VerboseMode.COSTS_AND_MOVES,
show = [
# DisplayOptions.ATOMS,
DisplayOptions.TREE,
],
);
return; return;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,51 @@
# Models #
- In this folder the configuration files for the models of data classes (as `*.yaml`-files) are stored.
- These are interpreted by `openapi` to generate the classes for the source code during the `build` phase of the programme.
- Once the models are generated, the app configurations are read at run time and interpreted as these models.
- Generating instead of manually encoding the classes is safer/more stable as it allows for validation.
## Folder structure ##
As per the 3rd point, the `yaml`-file/s for the models (schemata)
and the `yaml`-file/s for actual configuration values for the app
are to be kept separately.
All models are to be stored in [./models](../models/),
whereas configuration files are to be stored in [./assets](../assets/).
Note that the generated python files are not stored in the repository.
When deployed on a server, these are generated as part of the `build`-process.
## Developer notes ##
### Prerequisites ###
For the python source code, we currently use:
- Python: `v3.10.*`
- Modules:
- `datamodel-code-generator==0.12.0`
(This is all taken care of during the `build` process.)
### Building the models ###
To build the models before run time, use the following command + options:
```bash
datamodel-codegen
--input-file-type openapi
--encoding "UTF-8"
--disable-timestamp
--use-schema-description
--snake-case-field
--strict-nullable
--input <path/to/file.yml>
--output <path/to/file.py>
```
(cf. https://pydantic-docs.helpmanual.io/datamodel_code_generator/).
Alternatively, call:
```
just build
```

View File

@ -0,0 +1,140 @@
openapi: 3.0.3
info:
version: 0.1.0
title: Schemata for command instructions
servers: []
paths: {}
components:
schemas:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Commands
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Commands:
description: |-
List of commands to test algorithms/datastructures.
type: array
items:
$ref: "#/components/schemas/Command"
default: []
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Command
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command:
description: |-
Instructions for command to call
required:
- name
properties: &ref_command_properties
name:
$ref: '#/components/schemas/EnumAlgorithmNames'
additionalProperties: true
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Command - Algorithm: Tarjan
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CommandTarjan:
description: |-
Instructions for execution of Tarjan-Algorithm
type: object
required:
- name
properties:
<<: *ref_command_properties
# required:
# properties:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Command - Algorithm: TSP
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CommandTsp:
description: |-
Instructions for execution of TSP-Algorithm
type: object
required:
- name
- optimise
- dist
properties:
<<: *ref_command_properties
dist:
type: array
items:
type: array
items:
type: number
optimise:
$ref: '#/components/schemas/EnumTSPOptimise'
verbose:
type: boolean
default: false
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Command - Algorithm: Hirschberg
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CommandHirschberg:
description: |-
Instructions for execution of Hirschberg-Algorithm
type: object
required:
- name
- horizontal
- vertical
properties:
<<: *ref_command_properties
word1:
description: Word that gets placed vertically in algorithm.
type: string
word2:
description: Word that gets placed horizontally in algorithm
type: string
once:
type: boolean
default: false
verbose:
type: array
items:
$ref: '#/components/schemas/EnumHirschbergVerbosity'
default: []
show:
type: array
items:
$ref: '#/components/schemas/EnumHirschbergShow'
default: []
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Enum Algorithm Names
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EnumAlgorithmNames:
description: |-
Enumeration of possible algorithm options.
type: string
enum:
- TARJAN
- TSP
- HIRSCHBERG
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Enum TSP - Optimise Mode
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EnumTSPOptimise:
description: |-
Enumeration of optimisation options for TSP
type: string
enum:
- MIN
- MAX
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Enum Hirschberg - Verbosity options
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EnumHirschbergVerbosity:
description: |-
Enumeration of verbosity options for Hirschberg
type: string
enum:
- COSTS
- MOVES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Enum Hirschberg - display options
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EnumHirschbergShow:
description: |-
Enumeration of verbosity options for Hirschberg
type: string
enum:
- TREE
- ATOMS

View File

@ -0,0 +1,49 @@
openapi: 3.0.3
info:
version: 0.1.0
title: Schemata for config models
servers: []
paths: {}
components:
schemas:
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Config
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Config:
descripton: |-
Data model for all parts of the configuration.
type: object
required:
- info
- options
- calls
properties:
info:
$ref: "#/components/schemas/Info"
options:
$ref: "#/components/schemas/AppOptions"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Info
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Info:
description: |-
Contains meta data about project.
type: object
required:
- title
- description
- author
properties:
title:
type: string
description:
type: string
author:
type: string
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# App Options
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AppOptions:
description: |-
Options pertaining to the rudimentary setup of the app.
type: object

View File

@ -1,7 +1,35 @@
pip>=22.0.4 pip>=22.1.2
wheel>=0.37.1
# running
anyio>=3.5.0
aiohttp>=3.8.1
asyncio>=3.4.3
codetiming>=1.3.0
# testing + dev
coverage[toml]>=6.4
pytest>=7.1.1 pytest>=7.1.1
pytest-asyncio>=0.18.3
pytest-cov>=3.0.0
pytest-lazy-fixture>=0.6.3 pytest-lazy-fixture>=0.6.3
pytest-order>=1.0.1
testfixtures>=6.18.5
# config
python-dotenv>=0.2.0
jsonschema>=4.4.0
lazy-load>=0.8.2
pyyaml>=6.0
pydantic>=1.9.0
datamodel-code-generator>=0.12.0
# misc
lorem>=0.1.1
safetywrap>=1.5.0
typing>=3.7.4.3 typing>=3.7.4.3
# maths
numpy>=1.22.3 numpy>=1.22.3
pandas>=1.4.1 pandas>=1.4.1
tabulate>=0.8.9 tabulate>=0.8.9

View File

@ -15,6 +15,4 @@ from src.hirschberg.display import *;
__all__ = [ __all__ = [
'hirschberg_algorithm', 'hirschberg_algorithm',
'VerboseMode',
'DisplayOptions',
]; ];

View File

@ -8,6 +8,7 @@
from src.thirdparty.types import *; from src.thirdparty.types import *;
from src.thirdparty.maths import *; from src.thirdparty.maths import *;
from models.generated.commands import *;
from src.hirschberg.constants import *; from src.hirschberg.constants import *;
from src.hirschberg.display import *; from src.hirschberg.display import *;
from src.hirschberg.matrix import *; from src.hirschberg.matrix import *;
@ -30,8 +31,8 @@ __all__ = [
def simple_algorithm( def simple_algorithm(
X: str, X: str,
Y: str, Y: str,
verb: VerboseMode = VerboseMode.NONE, verb: List[EnumHirschbergVerbosity] = [],
show: List[DisplayOptions] = [], show: List[EnumHirschbergShow] = [],
) -> Tuple[str, str]: ) -> Tuple[str, str]:
''' '''
Dieser Algorithmus berechnet die Edit-Distanzen + optimale Richtungen ein Mal. Dieser Algorithmus berechnet die Edit-Distanzen + optimale Richtungen ein Mal.
@ -40,7 +41,7 @@ def simple_algorithm(
Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y); Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y);
path = reconstruct_optimal_path(Moves=Moves); path = reconstruct_optimal_path(Moves=Moves);
word_x, word_y = reconstruct_words(X = '-' + X, Y = '-' + Y, moves=[Moves[coord] for coord in path], path=path); word_x, word_y = reconstruct_words(X = '-' + X, Y = '-' + Y, moves=[Moves[coord] for coord in path], path=path);
if verb != VerboseMode.NONE: if verb != []:
repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y, verb=verb); repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y, verb=verb);
display = word_y + f'\n{"-"*len(word_x)}\n' + word_x; display = word_y + f'\n{"-"*len(word_x)}\n' + word_x;
print(f'\n{repr}\n\n\x1b[1mOptimales Alignment:\x1b[0m\n\n{display}\n'); print(f'\n{repr}\n\n\x1b[1mOptimales Alignment:\x1b[0m\n\n{display}\n');
@ -50,8 +51,8 @@ def hirschberg_algorithm(
X: str, X: str,
Y: str, Y: str,
once: bool = False, once: bool = False,
verb: VerboseMode = VerboseMode.NONE, verb: List[EnumHirschbergVerbosity] = [],
show: List[DisplayOptions] = [], show: List[EnumHirschbergShow] = [],
) -> Tuple[str, str]: ) -> Tuple[str, str]:
''' '''
Der Hirschberg-Algorithmus berechnet nur die Edit-Distanzen (Kostenmatrix) Der Hirschberg-Algorithmus berechnet nur die Edit-Distanzen (Kostenmatrix)
@ -72,8 +73,8 @@ def hirschberg_algorithm(
word_y = align.as_string2(); word_y = align.as_string2();
# verbose output hier behandeln (irrelevant für Algorithmus): # verbose output hier behandeln (irrelevant für Algorithmus):
if verb != VerboseMode.NONE: if verb != []:
if DisplayOptions.TREE in show: if EnumHirschbergShow.tree in show:
display = align.astree(braces=True); display = align.astree(braces=True);
else: else:
display_x = align.as_string1(braces=True); display_x = align.as_string1(braces=True);
@ -87,8 +88,8 @@ def hirschberg_algorithm_step(
X: str, X: str,
Y: str, Y: str,
depth: int = 0, depth: int = 0,
verb: VerboseMode = VerboseMode.NONE, verb: List[EnumHirschbergVerbosity] = [],
show: List[DisplayOptions] = [], show: List[EnumHirschbergShow] = [],
) -> Alignment: ) -> Alignment:
''' '''
Der rekursive Schritt der Hirschberg-Algorithmus teil eines der Wörter in zwei Der rekursive Schritt der Hirschberg-Algorithmus teil eines der Wörter in zwei
@ -105,7 +106,7 @@ def hirschberg_algorithm_step(
word_x, word_y = reconstruct_words(X = '-' + X, Y = '-' + Y, moves=[Moves[coord] for coord in path], path=path); word_x, word_y = reconstruct_words(X = '-' + X, Y = '-' + Y, moves=[Moves[coord] for coord in path], path=path);
# verbose output hier behandeln (irrelevant für Algorithmus): # verbose output hier behandeln (irrelevant für Algorithmus):
if verb != VerboseMode.NONE and (DisplayOptions.ATOMS in show): if verb != [] and (EnumHirschbergShow.atoms in show):
repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y, verb=verb); repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y, verb=verb);
print(f'\n\x1b[1mRekursionstiefe: {depth}\x1b[0m\n\n{repr}') print(f'\n\x1b[1mRekursionstiefe: {depth}\x1b[0m\n\n{repr}')
@ -126,7 +127,7 @@ def hirschberg_algorithm_step(
Costs2, Moves2 = compute_cost_matrix(X = '-' + X2, Y = '-' + Y2); Costs2, Moves2 = compute_cost_matrix(X = '-' + X2, Y = '-' + Y2);
# verbose output hier behandeln (irrelevant für Algorithmus): # verbose output hier behandeln (irrelevant für Algorithmus):
if verb != VerboseMode.NONE: if verb != []:
path1, path2 = reconstruct_optimal_path_halves(Costs1=Costs1, Costs2=Costs2, Moves1=Moves1, Moves2=Moves2); path1, path2 = reconstruct_optimal_path_halves(Costs1=Costs1, Costs2=Costs2, Moves1=Moves1, Moves2=Moves2);
repr = display_cost_matrix_halves( repr = display_cost_matrix_halves(
Costs1 = Costs1, Costs1 = Costs1,

View File

@ -12,8 +12,6 @@ from src.thirdparty.types import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [ __all__ = [
'VerboseMode',
'DisplayOptions',
'Directions', 'Directions',
'gap_penalty', 'gap_penalty',
'missmatch_penalty', 'missmatch_penalty',
@ -23,16 +21,6 @@ __all__ = [
# ENUMS # ENUMS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class VerboseMode(Enum):
NONE = -1;
COSTS = 0;
MOVES = 1;
COSTS_AND_MOVES = 2;
class DisplayOptions(Enum):
TREE = 0;
ATOMS = 1;
class Directions(Enum): class Directions(Enum):
UNSET = -1; UNSET = -1;
# Prioritäten hier setzen # Prioritäten hier setzen

View File

@ -8,6 +8,7 @@
from src.thirdparty.types import *; from src.thirdparty.types import *;
from src.thirdparty.maths import *; from src.thirdparty.maths import *;
from models.generated.commands import *;
from src.hirschberg.constants import *; from src.hirschberg.constants import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -29,7 +30,7 @@ def represent_cost_matrix(
path: List[Tuple[int, int]], path: List[Tuple[int, int]],
X: str, X: str,
Y: str, Y: str,
verb: VerboseMode, verb: List[EnumHirschbergVerbosity],
pad: bool = False, pad: bool = False,
) -> np.ndarray: # NDArray[(Any, Any), Any]: ) -> np.ndarray: # NDArray[(Any, Any), Any]:
m = len(X); # display vertically m = len(X); # display vertically
@ -42,28 +43,27 @@ def represent_cost_matrix(
table = np.full(shape=(3 + m, 3 + n), dtype=object, fill_value=''); table = np.full(shape=(3 + m, 3 + n), dtype=object, fill_value='');
# topmost rows: # topmost rows:
table[0, 3:(3+n)] = [str(j) for j in range(n)]; table[0, 3:(3+n)] = [ f'\x1b[2m{j}\x1b[0m' for j in range(n) ];
table[1, 3:(3+n)] = [y for y in Y]; table[1, 3:(3+n)] = [ f'\x1b[1m{y}\x1b[0m' for y in Y ];
table[2, 3:(3+n)] = '--'; table[2, 3:(3+n)] = '--';
# leftmost columns: # leftmost columns:
table[3:(3+m), 0] = [str(i) for i in range(m)]; table[3:(3+m), 0] = [ f'\x1b[2m{i}\x1b[0m' for i in range(m) ];
table[3:(3+m), 1] = [x for x in X]; table[3:(3+m), 1] = [ f'\x1b[1m{x}\x1b[0m' for x in X ];
table[3:(3+m), 2] = '|'; table[3:(3+m), 2] = '|';
if pad: if pad:
table[-3, 3:(3+n)] = '--'; table[-3, 3:(3+n)] = '--';
table[3:(3+m), -1] = '|'; table[3:(3+m), -1] = '|';
match verb: if EnumHirschbergVerbosity.costs in verb:
case VerboseMode.MOVES: table[3:(3+m), 3:(3+n)] = Costs.copy();
table[3:(3+m), 3:(3+n)] = '\x1b[2m.\x1b[0m'; if EnumHirschbergVerbosity.moves in verb:
for (i, j) in path: for (i, j) in path:
table[3 + i, 3 + j] = '\x1b[31;1m*\x1b[0m'; table[3 + i, 3 + j] = f'\x1b[31;4;1m{table[3 + i, 3 + j]}\x1b[0m';
case VerboseMode.COSTS | VerboseMode.COSTS_AND_MOVES: elif EnumHirschbergVerbosity.moves in verb:
table[3:(3+m), 3:(3+n)] = Costs.copy(); table[3:(3+m), 3:(3+n)] = '\x1b[2m.\x1b[0m';
if verb == VerboseMode.COSTS_AND_MOVES: for (i, j) in path:
for (i, j) in path: table[3 + i, 3 + j] = '\x1b[31;1m*\x1b[0m';
table[3 + i, 3 + j] = f'\x1b[31;4;1m{table[3 + i, 3 + j]}\x1b[0m';
return table; return table;
@ -72,7 +72,7 @@ def display_cost_matrix(
path: List[Tuple[int, int]], path: List[Tuple[int, int]],
X: str, X: str,
Y: str, Y: str,
verb: VerboseMode, verb: EnumHirschbergVerbosity,
) -> str: ) -> str:
''' '''
Zeigt Kostenmatrix + optimalen Pfad. Zeigt Kostenmatrix + optimalen Pfad.
@ -99,7 +99,7 @@ def display_cost_matrix_halves(
X2: str, X2: str,
Y1: str, Y1: str,
Y2: str, Y2: str,
verb: VerboseMode, verb: EnumHirschbergVerbosity,
) -> str: ) -> str:
''' '''
Zeigt Kostenmatrix + optimalen Pfad für Schritt im D & C Hirschberg-Algorithmus Zeigt Kostenmatrix + optimalen Pfad für Schritt im D & C Hirschberg-Algorithmus

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.misc import *;
from src.thirdparty.config import *;
from src.thirdparty.code import *;
from src.thirdparty.types import *;
from models.generated.config import *;
from models.generated.commands import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'INFO',
'OPTIONS',
'COMMANDS',
];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PATH_ASSETS_CONFIG: str = 'assets/config.yaml';
PATH_ASSETS_COMMANDS: str = 'assets/commands.yaml';
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# LAZY LOADED RESOURCES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def load_assets_config(path: str) -> Config: # pragma: no cover
with open(path, 'r') as fp:
assets = yaml_load(fp, Loader=yaml_FullLoader);
assert isinstance(assets, dict);
return Config(**assets);
def create_commands(path: str) -> List[Command]: # pragma: no cover
commands = [];
with open(path, 'r') as fp:
assets = yaml_load(fp, Loader=yaml_FullLoader);
for command in assets:
match Command(**command).name:
case EnumAlgorithmNames.tarjan:
commands.append(CommandTarjan(**command));
case EnumAlgorithmNames.tsp:
commands.append(CommandTsp(**command));
case EnumAlgorithmNames.hirschberg:
commands.append(CommandHirschberg(**command));
return commands;
# use lazy loaing to ensure that values only loaded (once) when used
CONFIG: Config = lazy(load_assets_config, path=PATH_ASSETS_CONFIG);
INFO: Info = lazy(lambda x: x.info, CONFIG);
OPTIONS: AppOptions = lazy(lambda x: x.options, CONFIG);
COMMANDS: List[Command] = lazy(create_commands, path=PATH_ASSETS_COMMANDS);

43
code/python/src/thirdparty/code.py vendored Normal file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from functools import wraps;
from functools import partial;
from dataclasses import dataclass;
from dataclasses import field;
from dataclasses import Field;
from dataclasses import asdict;
from dataclasses import MISSING;
from itertools import product as itertools_product;
# cf. https://github.com/mplanchard/safetywrap
from safetywrap import Ok;
from safetywrap import Err;
from safetywrap import Nothing;
from safetywrap import Result;
from safetywrap import Option;
from safetywrap import Some;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'partial',
'wraps',
'asdict',
'dataclass',
'field',
'Field',
'MISSING',
'itertools_product',
'Err',
'Nothing',
'Ok',
'Option',
'Result',
'Some',
];

View File

@ -6,10 +6,12 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
import json; import json;
import jsonschema;
from lazy_load import lazy;
from yaml import add_constructor; from yaml import add_constructor;
from yaml import load; from yaml import load as yaml_load;
from yaml import Loader; from yaml import FullLoader as yaml_FullLoader;
from yaml import FullLoader; from yaml import add_path_resolver as yaml_add_path_resolver;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS # EXPORTS
@ -17,8 +19,10 @@ from yaml import FullLoader;
__all__ = [ __all__ = [
'json', 'json',
'jsonschema',
'lazy',
'add_constructor', 'add_constructor',
'load', 'yaml_load',
'Loader', 'yaml_FullLoader',
'FullLoader', 'yaml_add_path_resolver',
]; ];

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.tsp.algorithms import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'tsp_algorithm',
];

View File

@ -5,7 +5,6 @@
# IMPORTS # IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations;
from src.thirdparty.types import *; from src.thirdparty.types import *;
from src.thirdparty.maths import *; from src.thirdparty.maths import *;
@ -14,15 +13,15 @@ from src.thirdparty.maths import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [ __all__ = [
'tsp_naive_algorithm', 'tsp_algorithm',
]; ];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHOD tsp_naive_algorithm # METHOD tsp_algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def tsp_naive_algorithm( def tsp_algorithm(
dist: NDArray[(Any, Any), float], dist: np.ndarray, # NDArray[(Any, Any), float],
optimise = min, optimise = min,
verbose: bool = False, verbose: bool = False,
) -> tuple[float, list[list[int]]]: ) -> tuple[float, list[list[int]]]: