Compare commits
No commits in common. "760bff11f28747a48d9287c8da25fa456a77495b" and "67aa70edfa45f33f5aceb228744fec093f86cc99" have entirely different histories.
760bff11f2
...
67aa70edfa
@ -1,18 +0,0 @@
|
|||||||
[run]
|
|
||||||
source="."
|
|
||||||
|
|
||||||
[report]
|
|
||||||
show_missing = true
|
|
||||||
omit =
|
|
||||||
# ignore tests folder
|
|
||||||
tests/*
|
|
||||||
# ignore thirdparty imports
|
|
||||||
src/thirdparty/*
|
|
||||||
# ignore __init__ files (only used for exports)
|
|
||||||
**/__init__.py
|
|
||||||
# ignore main.py
|
|
||||||
main.py
|
|
||||||
# TODO: increase code-coverage:
|
|
||||||
precision = 0
|
|
||||||
exclude_lines =
|
|
||||||
pragma: no cover
|
|
6
code/python/.gitignore
vendored
6
code/python/.gitignore
vendored
@ -6,11 +6,10 @@
|
|||||||
################################################################
|
################################################################
|
||||||
|
|
||||||
!/.env
|
!/.env
|
||||||
!/justfile
|
!/Makefile
|
||||||
!/.coveragerc
|
|
||||||
!/README.md
|
!/README.md
|
||||||
!/LICENSE
|
!/LICENSE
|
||||||
!/requirements.txt
|
!/requirements
|
||||||
!/pyproject.toml
|
!/pyproject.toml
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
@ -20,7 +19,6 @@
|
|||||||
!/src
|
!/src
|
||||||
!/src/**/
|
!/src/**/
|
||||||
!/src/**/*.py
|
!/src/**/*.py
|
||||||
!/main.py
|
|
||||||
|
|
||||||
!/tests
|
!/tests
|
||||||
!/tests/**/
|
!/tests/**/
|
||||||
|
94
code/python/Makefile
Normal file
94
code/python/Makefile
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
SHELL:=/usr/bin/env bash
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# Makefile
|
||||||
|
# NOTE: Do not change the contents of this file!
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
include .env
|
||||||
|
|
||||||
|
################################
|
||||||
|
# VARIABLES
|
||||||
|
################################
|
||||||
|
|
||||||
|
ARTEFACT_NAME:=${APPNAME}
|
||||||
|
PYTHON:=python3
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
ARTEFACT_NAME:=${APPNAME}.exe
|
||||||
|
PYTHON=py -3
|
||||||
|
endif
|
||||||
|
|
||||||
|
################################
|
||||||
|
# Macros
|
||||||
|
################################
|
||||||
|
|
||||||
|
define create_file_if_not_exists
|
||||||
|
@touch "$(1)";
|
||||||
|
endef
|
||||||
|
|
||||||
|
define create_folder_if_not_exists
|
||||||
|
if ! [ -d "$(1)" ]; then mkdir "$(1)"; fi
|
||||||
|
endef
|
||||||
|
|
||||||
|
define delete_if_file_exists
|
||||||
|
@if [ -f "$(1)" ]; then rm "$(1)"; fi
|
||||||
|
endef
|
||||||
|
|
||||||
|
define delete_if_folder_exists
|
||||||
|
@if [ -d "$(1)" ]; then rm -rf "$(1)"; fi
|
||||||
|
endef
|
||||||
|
|
||||||
|
define clean_all_files
|
||||||
|
@find . -type f -name "$(1)" -exec basename {} \;
|
||||||
|
@find . -type f -name "$(1)" -exec rm {} \; 2> /dev/null
|
||||||
|
endef
|
||||||
|
|
||||||
|
define clean_all_folders
|
||||||
|
@find . -type d -name "$(1)" -exec basename {} \;
|
||||||
|
@find . -type d -name "$(1)" -exec rm -rf {} \; 2> /dev/null
|
||||||
|
endef
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# TARGETS
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
################################
|
||||||
|
# BASIC TARGETS: setup, build, run
|
||||||
|
################################
|
||||||
|
setup: check-system-requirements setup-no-checks
|
||||||
|
setup-no-checks:
|
||||||
|
@${PYTHON} -m pip install -r "requirements"
|
||||||
|
run:
|
||||||
|
@${PYTHON} src/main.py;
|
||||||
|
all: setup run
|
||||||
|
################################
|
||||||
|
# TARGETS: testing
|
||||||
|
################################
|
||||||
|
tests: unit-tests
|
||||||
|
unit-tests:
|
||||||
|
@# For logging purposes (since stdout is rechanneled):
|
||||||
|
@$(call delete_if_file_exists,logs/debug.log)
|
||||||
|
@$(call create_folder_if_not_exists,logs)
|
||||||
|
@$(call create_file_if_not_exists,logs/debug.log)
|
||||||
|
@# for python unit tests:
|
||||||
|
@${PYTHON} -m pytest tests --cache-clear --verbose -k test_
|
||||||
|
@cat logs/debug.log
|
||||||
|
################################
|
||||||
|
# AUXILIARY (INTERNAL TARGETS)
|
||||||
|
################################
|
||||||
|
check-system-requirements:
|
||||||
|
@if ! ( ${PYTHON} --version >> /dev/null 2> /dev/null ); then \
|
||||||
|
echo "Install Python 3.10.x first!"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@${PYTHON} --version
|
||||||
|
################################
|
||||||
|
# TARGETS: clean
|
||||||
|
################################
|
||||||
|
clean:
|
||||||
|
@echo "All system artefacts will be force removed."
|
||||||
|
@$(call clean_all_files,.DS_Store)
|
||||||
|
@echo "All build artefacts will be force removed."
|
||||||
|
@$(call clean_all_folders,__pycache__)
|
||||||
|
@$(call clean_all_folders,.pytest_cache)
|
||||||
|
@$(call delete_if_file_exists,dist/${ARTEFACT_NAME})
|
||||||
|
@exit 0
|
@ -10,7 +10,9 @@ die Methoden mit Daten ausprobieren.
|
|||||||
## Voraussetzungen ##
|
## Voraussetzungen ##
|
||||||
|
|
||||||
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, **`make`** zu installieren.
|
||||||
|
- Linux/OSX: siehe <https://formulae.brew.sh/formula/make>.
|
||||||
|
- Windows: siehe <https://community.chocolatey.org/packages/make>.
|
||||||
|
|
||||||
## Setup -> Test -> Run ##
|
## Setup -> Test -> Run ##
|
||||||
|
|
||||||
@ -18,27 +20,25 @@ In einem IDE in dem Repo zu diesem Ordner navigieren.
|
|||||||
</br>
|
</br>
|
||||||
Eine bash-Konsole aufmachen und folgende Befehle ausführen:
|
Eine bash-Konsole aufmachen und folgende Befehle ausführen:
|
||||||
|
|
||||||
Wer das **justfile**-Tool hat:
|
Wer **make** installiert hat:
|
||||||
```bash
|
```bash
|
||||||
# Zeige alle Befehle:
|
|
||||||
just
|
|
||||||
# Zur Installation der Requirements (nur nach Änderungen):
|
# Zur Installation der Requirements (nur nach Änderungen):
|
||||||
just setup;
|
make setup;
|
||||||
# Zur Ausführung der unit tests:
|
# Zur Ausführung der unit tests:
|
||||||
just tests;
|
make tests;
|
||||||
# Zur Ausführung des Programms
|
# Zur Ausführung des Programms
|
||||||
just run;
|
make run;
|
||||||
# Zur Bereinigung aller Artefakte
|
# Zur Bereinigung aller Artefakte
|
||||||
just clean;
|
make clean;
|
||||||
```
|
```
|
||||||
Wer das justfile-Tool hat:
|
Wer _kein_ make hat:
|
||||||
```bash
|
```bash
|
||||||
# Zur Installation der Requirements (nur nach Änderungen):
|
# Zur Installation der Requirements (nur nach Änderungen):
|
||||||
python3 -m pip install -r requirements.txt;
|
python3 -m pip install -r requirements;
|
||||||
# Zur Ausführung der unit tests:
|
# Zur Ausführung der unit tests:
|
||||||
python3 -m pytest tests --cache-clear --verbose -k test_;
|
python3 -m pytest tests --cache-clear --verbose -k test_;
|
||||||
# Zur Ausführung des Programms:
|
# Zur Ausführung des Programms:
|
||||||
python3 main.py
|
python3 src/main.py
|
||||||
```
|
```
|
||||||
Auf Windows verwendet man `py -3` od. `py -310` statt `python3`.
|
Auf Windows verwendet man `py -3` od. `py -310` statt `python3`.
|
||||||
|
|
||||||
|
@ -1,147 +0,0 @@
|
|||||||
set shell := [ "bash", "-uc" ]
|
|
||||||
_default:
|
|
||||||
@- just --unsorted --choose
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# Justfile
|
|
||||||
# NOTE: Do not change the contents of this file!
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# VARIABLES
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
PYTHON := if os_family() == "windows" { "py -3" } else { "python3" }
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# Macros
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
_create-file-if-not-exists fname:
|
|
||||||
@touch "{{fname}}";
|
|
||||||
|
|
||||||
_create-folder-if-not-exists path:
|
|
||||||
@if ! [ -d "{{path}}" ]; then mkdir "{{path}}"; fi
|
|
||||||
|
|
||||||
_delete-if-file-exists fname:
|
|
||||||
@if [ -f "{{fname}}" ]; then rm "{{fname}}"; fi
|
|
||||||
|
|
||||||
_delete-if-folder-exists path:
|
|
||||||
@if [ -d "{{path}}" ]; then rm -rf "{{path}}"; fi
|
|
||||||
|
|
||||||
_clean-all-files pattern:
|
|
||||||
@find . -type f -name "{{pattern}}" -exec basename {} \; 2> /dev/null
|
|
||||||
@- find . -type f -name "{{pattern}}" -exec rm {} \; 2> /dev/null
|
|
||||||
|
|
||||||
_clean-all-folders pattern:
|
|
||||||
@find . -type d -name "{{pattern}}" -exec basename {} \; 2> /dev/null
|
|
||||||
@- find . -type d -name "{{pattern}}" -exec rm -rf {} \; 2> /dev/null
|
|
||||||
|
|
||||||
_docker-build-and-log service:
|
|
||||||
@docker compose up --build -d {{service}} && docker compose logs -f --tail=0 {{service}}
|
|
||||||
|
|
||||||
_docker-build-and-interact service container:
|
|
||||||
@docker compose up --build -d {{service}} && docker attach {{container}}
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: build
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
build:
|
|
||||||
@{{PYTHON}} -m pip install --disable-pip-version-check -r requirements.txt
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: run
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
run:
|
|
||||||
@{{PYTHON}} main.py
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: tests
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
tests: tests-unit
|
|
||||||
tests-logs:
|
|
||||||
@just _create-logs
|
|
||||||
@- just tests
|
|
||||||
@just _display-logs
|
|
||||||
tests-unit-logs:
|
|
||||||
@just _create-logs
|
|
||||||
@- just tests-unit
|
|
||||||
@just _display-logs
|
|
||||||
tests-unit:
|
|
||||||
@{{PYTHON}} -m pytest tests \
|
|
||||||
--ignore=tests/integration \
|
|
||||||
--cov-reset \
|
|
||||||
--cov=. \
|
|
||||||
2> /dev/null
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: qa
|
|
||||||
# NOTE: use for development only.
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
qa:
|
|
||||||
@{{PYTHON}} -m coverage report -m
|
|
||||||
coverage source_path tests_path:
|
|
||||||
@just _create-logs
|
|
||||||
@-just _coverage-no-logs "{{source_path}}" "{{tests_path}}"
|
|
||||||
@just _display-logs
|
|
||||||
_coverage-no-logs source_path tests_path:
|
|
||||||
@{{PYTHON}} -m pytest {{tests_path}} \
|
|
||||||
--ignore=tests/integration \
|
|
||||||
--cov-reset \
|
|
||||||
--cov={{source_path}} \
|
|
||||||
--capture=tee-sys \
|
|
||||||
2> /dev/null
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: clean
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
clean:
|
|
||||||
@-just clean-basic
|
|
||||||
@-just clean-sessions
|
|
||||||
clean-sessions:
|
|
||||||
@echo "All sessions will be force removed."
|
|
||||||
@- just _delete-if-folder-exists ".secrets" 2> /dev/null
|
|
||||||
clean-basic:
|
|
||||||
@echo "All system artefacts will be force removed."
|
|
||||||
@- just _clean-all-files ".DS_Store" 2> /dev/null
|
|
||||||
@echo "All test artefacts will be force removed."
|
|
||||||
@- just _clean-all-folders ".pytest_cache" 2> /dev/null
|
|
||||||
@- just _delete-if-file-exists ".coverage" 2> /dev/null
|
|
||||||
@- just _delete-if-folder-exists "logs"
|
|
||||||
@echo "All build artefacts will be force removed."
|
|
||||||
@- just _clean-all-folders "__pycache__" 2> /dev/null
|
|
||||||
@- just _delete-if-folder-exists "models/generated" 2> /dev/null
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: logging, session
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
_create-logs:
|
|
||||||
@# For logging purposes (since stdout is rechanneled):
|
|
||||||
@just _delete-if-file-exists "logs/debug.log"
|
|
||||||
@just _create-folder-if-not-exists "logs"
|
|
||||||
@just _create-file-if-not-exists "logs/debug.log"
|
|
||||||
_display-logs:
|
|
||||||
@echo ""
|
|
||||||
@echo "Content of logs/debug.log:"
|
|
||||||
@echo "----------------"
|
|
||||||
@echo ""
|
|
||||||
@- cat logs/debug.log
|
|
||||||
@echo ""
|
|
||||||
@echo "----------------"
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# TARGETS: requirements
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
check-system:
|
|
||||||
@echo "Operating System detected: {{os_family()}}."
|
|
||||||
@echo "Python command used: {{PYTHON}}."
|
|
@ -1,56 +1,8 @@
|
|||||||
[project]
|
|
||||||
name = "uni-leipzig-ads-2-2022"
|
|
||||||
version = "1.0.0"
|
|
||||||
description = "Zusatzcode, um Algorithmen und Datenstrukturen im Kurs ADS2 zu demonstrieren."
|
|
||||||
authors = [ "Raj Dahya" ]
|
|
||||||
maintainers = [ "raj_mathe" ]
|
|
||||||
license = "MIT"
|
|
||||||
readme = "README.md"
|
|
||||||
python = "^3.10"
|
|
||||||
homepage = "https://gitea.math.uni-leipzig.de/raj_mathe"
|
|
||||||
repository = "https://gitea.math.uni-leipzig.de/raj_mathe/ads2_2022"
|
|
||||||
documentation = "https://gitea.math.uni-leipzig.de/raj_mathe/ads2_2022/README.md"
|
|
||||||
keywords = [
|
|
||||||
"algorithmmen und datenstrukturen 2",
|
|
||||||
"sommersemester",
|
|
||||||
"2022",
|
|
||||||
"universität leipzig",
|
|
||||||
]
|
|
||||||
# cf. https://pypi.org/classifiers
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Environment :: Console",
|
|
||||||
"Intended Audience :: Developers",
|
|
||||||
"Operating System :: Unix",
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "7.1.1"
|
minversion = "7.1.1"
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"tests",
|
"tests",
|
||||||
]
|
]
|
||||||
python_files = [
|
python_files = [
|
||||||
"**/tests_*.py",
|
"**/test_*.py",
|
||||||
]
|
|
||||||
asyncio_mode = "auto"
|
|
||||||
filterwarnings = [
|
|
||||||
"error",
|
|
||||||
"ignore::UserWarning",
|
|
||||||
"ignore::DeprecationWarning",
|
|
||||||
]
|
|
||||||
# NOTE: appends (not prepends) flags:
|
|
||||||
addopts = [
|
|
||||||
"--order-dependencies",
|
|
||||||
"--order-group-scope=module",
|
|
||||||
"--cache-clear",
|
|
||||||
"--verbose",
|
|
||||||
"--maxfail=1",
|
|
||||||
"-k test_",
|
|
||||||
"--no-cov-on-fail",
|
|
||||||
"--cov-report=term",
|
|
||||||
"--cov-config=.coveragerc",
|
|
||||||
]
|
]
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations;
|
from __future__ import annotations;
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
from src.local.typing import *;
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# EXPORTS
|
# EXPORTS
|
||||||
|
@ -6,8 +6,12 @@
|
|||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
from __future__ import annotations;
|
from __future__ import annotations;
|
||||||
|
from enum import Enum;
|
||||||
|
from dataclasses import dataclass;
|
||||||
|
from dataclasses import field
|
||||||
|
from platform import node;
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
from src.local.typing import *;
|
||||||
|
|
||||||
from src.core.log import *;
|
from src.core.log import *;
|
||||||
from src.stacks.stack import *;
|
from src.stacks.stack import *;
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.hirschberg.algorithms import *;
|
|
||||||
from src.hirschberg.constants import *;
|
|
||||||
from src.hirschberg.display import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'hirschberg_algorithm',
|
|
||||||
'VerboseMode',
|
|
||||||
'DisplayOptions',
|
|
||||||
];
|
|
@ -1,152 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
from src.thirdparty.maths import *;
|
|
||||||
|
|
||||||
from src.hirschberg.constants import *;
|
|
||||||
from src.hirschberg.display import *;
|
|
||||||
from src.hirschberg.matrix import *;
|
|
||||||
from src.hirschberg.paths import *;
|
|
||||||
from src.hirschberg.types import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'hirschberg_algorithm',
|
|
||||||
'simple_algorithm',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHOD hirschberg_algorithm
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def simple_algorithm(
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
verb: VerboseMode = VerboseMode.NONE,
|
|
||||||
show: List[DisplayOptions] = [],
|
|
||||||
) -> Tuple[str, str]:
|
|
||||||
'''
|
|
||||||
Dieser Algorithmus berechnet die Edit-Distanzen + optimale Richtungen ein Mal.
|
|
||||||
Darus wird ein optimales Alignment direkt abgeleitet.
|
|
||||||
'''
|
|
||||||
Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y);
|
|
||||||
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);
|
|
||||||
if verb != VerboseMode.NONE:
|
|
||||||
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;
|
|
||||||
print(f'\n{repr}\n\n\x1b[1mOptimales Alignment:\x1b[0m\n\n{display}\n');
|
|
||||||
return word_x, word_y;
|
|
||||||
|
|
||||||
def hirschberg_algorithm(
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
once: bool = False,
|
|
||||||
verb: VerboseMode = VerboseMode.NONE,
|
|
||||||
show: List[DisplayOptions] = [],
|
|
||||||
) -> Tuple[str, str]:
|
|
||||||
'''
|
|
||||||
Der Hirschberg-Algorithmus berechnet nur die Edit-Distanzen (Kostenmatrix)
|
|
||||||
und weder speichert noch berechnet die Matrix der optimalen Richtungen.
|
|
||||||
|
|
||||||
Dies liefert eine Platz-effizientere Methode als die simple Methode.
|
|
||||||
|
|
||||||
Durch Rekursion wird eine Art Traceback durch die zugrunde liegende DP erreicht.
|
|
||||||
Daraus wird unmittelbar ein optimales Alignment bestimmt.
|
|
||||||
Des Weiteren werden Zeitkosten durch Divide-and-Conquer klein gehalten.
|
|
||||||
'''
|
|
||||||
# ggf. nur den simplen Algorithmus ausführen:
|
|
||||||
if once:
|
|
||||||
return simple_algorithm(X=X, Y=Y, verb=verb, show=show);
|
|
||||||
|
|
||||||
align = hirschberg_algorithm_step(X=X, Y=Y, depth=1, verb=verb, show=show);
|
|
||||||
word_x = align.as_string1();
|
|
||||||
word_y = align.as_string2();
|
|
||||||
|
|
||||||
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
||||||
if verb != VerboseMode.NONE:
|
|
||||||
if DisplayOptions.TREE in show:
|
|
||||||
display = align.astree(braces=True);
|
|
||||||
else:
|
|
||||||
display_x = align.as_string1(braces=True);
|
|
||||||
display_y = align.as_string2(braces=True);
|
|
||||||
display = display_y + f'\n{"-"*len(display_x)}\n' + display_x;
|
|
||||||
print(f'\n\x1b[1mOptimales Alignment:\x1b[0m\n\n{display}\n');
|
|
||||||
|
|
||||||
return word_x, word_y;
|
|
||||||
|
|
||||||
def hirschberg_algorithm_step(
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
depth: int = 0,
|
|
||||||
verb: VerboseMode = VerboseMode.NONE,
|
|
||||||
show: List[DisplayOptions] = [],
|
|
||||||
) -> Alignment:
|
|
||||||
'''
|
|
||||||
Der rekursive Schritt der Hirschberg-Algorithmus teil eines der Wörter in zwei
|
|
||||||
und bestimmt eine entsprechende Aufteilung des zweiten Wortes in zwei,
|
|
||||||
die die Edit-Distanz minimiert.
|
|
||||||
|
|
||||||
Dies liefert uns Information über eine Stelle des optimalen Pfads durch die Kostenmatrix
|
|
||||||
sowie eine Aufteilung des Problems in eine linke und rechte Hälfte.
|
|
||||||
'''
|
|
||||||
n = len(Y);
|
|
||||||
if n == 1:
|
|
||||||
Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y);
|
|
||||||
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);
|
|
||||||
|
|
||||||
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
||||||
if verb != VerboseMode.NONE and (DisplayOptions.ATOMS in show):
|
|
||||||
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}')
|
|
||||||
|
|
||||||
return AlignmentBasic(word1=word_x, word2=word_y);
|
|
||||||
else:
|
|
||||||
n = int(np.ceil(n/2));
|
|
||||||
|
|
||||||
# bilde linke Hälfte vom horizontalen Wort:
|
|
||||||
Y1 = Y[:n];
|
|
||||||
X1 = X;
|
|
||||||
|
|
||||||
# bilde rechte Hälfte vom horizontalen Wort (und kehre h. + v. um):
|
|
||||||
Y2 = Y[n:][::-1];
|
|
||||||
X2 = X[::-1];
|
|
||||||
|
|
||||||
# Löse Teilprobleme:
|
|
||||||
Costs1, Moves1 = compute_cost_matrix(X = '-' + X1, Y = '-' + Y1);
|
|
||||||
Costs2, Moves2 = compute_cost_matrix(X = '-' + X2, Y = '-' + Y2);
|
|
||||||
|
|
||||||
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
||||||
if verb != VerboseMode.NONE:
|
|
||||||
path1, path2 = reconstruct_optimal_path_halves(Costs1=Costs1, Costs2=Costs2, Moves1=Moves1, Moves2=Moves2);
|
|
||||||
repr = display_cost_matrix_halves(
|
|
||||||
Costs1 = Costs1,
|
|
||||||
Costs2 = Costs2,
|
|
||||||
path1 = path1,
|
|
||||||
path2 = path2,
|
|
||||||
X1 = '-' + X1,
|
|
||||||
X2 = '-' + X2,
|
|
||||||
Y1 = '-' + Y1,
|
|
||||||
Y2 = '-' + Y2,
|
|
||||||
verb = verb,
|
|
||||||
);
|
|
||||||
print(f'\n\x1b[1mRekursionstiefe: {depth}\x1b[0m\n\n{repr}')
|
|
||||||
|
|
||||||
# Koordinaten des optimalen Übergangs berechnen:
|
|
||||||
coord1, coord2 = get_optimal_transition(Costs1=Costs1, Costs2=Costs2);
|
|
||||||
p = coord1[0];
|
|
||||||
# Divide and Conquer ausführen:
|
|
||||||
align_left = hirschberg_algorithm_step(X=X[:p], Y=Y[:n], depth=depth+1, verb=verb, show=show);
|
|
||||||
align_right = hirschberg_algorithm_step(X=X[p:], Y=Y[n:], depth=depth+1, verb=verb, show=show);
|
|
||||||
|
|
||||||
# Resultate zusammensetzen:
|
|
||||||
return AlignmentPair(left=align_left, right=align_right);
|
|
@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'VerboseMode',
|
|
||||||
'DisplayOptions',
|
|
||||||
'Directions',
|
|
||||||
'gap_penalty',
|
|
||||||
'missmatch_penalty',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# ENUMS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
class VerboseMode(Enum):
|
|
||||||
NONE = -1;
|
|
||||||
COSTS = 0;
|
|
||||||
MOVES = 1;
|
|
||||||
COSTS_AND_MOVES = 2;
|
|
||||||
|
|
||||||
class DisplayOptions(Enum):
|
|
||||||
TREE = 0;
|
|
||||||
ATOMS = 1;
|
|
||||||
|
|
||||||
class Directions(Enum):
|
|
||||||
UNSET = -1;
|
|
||||||
# Prioritäten hier setzen
|
|
||||||
DIAGONAL = 0;
|
|
||||||
HORIZONTAL = 1;
|
|
||||||
VERTICAL = 2;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# PENALTIES
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def gap_penalty(x: str):
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
def missmatch_penalty(x: str, y: str):
|
|
||||||
return 0 if x == y else 1;
|
|
@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
from src.thirdparty.maths import *;
|
|
||||||
|
|
||||||
from src.hirschberg.constants import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'represent_cost_matrix',
|
|
||||||
'display_cost_matrix',
|
|
||||||
'display_cost_matrix_halves',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHODS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def represent_cost_matrix(
|
|
||||||
Costs: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
path: List[Tuple[int, int]],
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
verb: VerboseMode,
|
|
||||||
pad: bool = False,
|
|
||||||
) -> np.ndarray: # NDArray[(Any, Any), Any]:
|
|
||||||
m = len(X); # display vertically
|
|
||||||
n = len(Y); # display horizontally
|
|
||||||
|
|
||||||
# erstelle string-Array:
|
|
||||||
if pad:
|
|
||||||
table = np.full(shape=(3 + m + 3, 3 + n + 1), dtype=object, fill_value='');
|
|
||||||
else:
|
|
||||||
table = np.full(shape=(3 + m, 3 + n), dtype=object, fill_value='');
|
|
||||||
|
|
||||||
# topmost rows:
|
|
||||||
table[0, 3:(3+n)] = [str(j) for j in range(n)];
|
|
||||||
table[1, 3:(3+n)] = [y for y in Y];
|
|
||||||
table[2, 3:(3+n)] = '--';
|
|
||||||
# leftmost columns:
|
|
||||||
table[3:(3+m), 0] = [str(i) for i in range(m)];
|
|
||||||
table[3:(3+m), 1] = [x for x in X];
|
|
||||||
table[3:(3+m), 2] = '|';
|
|
||||||
|
|
||||||
if pad:
|
|
||||||
table[-3, 3:(3+n)] = '--';
|
|
||||||
table[3:(3+m), -1] = '|';
|
|
||||||
|
|
||||||
match verb:
|
|
||||||
case VerboseMode.MOVES:
|
|
||||||
table[3:(3+m), 3:(3+n)] = '.';
|
|
||||||
for (i, j) in path:
|
|
||||||
table[3 + i, 3 + j] = '*';
|
|
||||||
case VerboseMode.COSTS | VerboseMode.COSTS_AND_MOVES:
|
|
||||||
table[3:(3+m), 3:(3+n)] = Costs.copy();
|
|
||||||
if verb == VerboseMode.COSTS_AND_MOVES:
|
|
||||||
for (i, j) in path:
|
|
||||||
table[3 + i, 3 + j] = f'\x1b[31;4;1m{table[3 + i, 3 + j]}\x1b[0m';
|
|
||||||
|
|
||||||
return table;
|
|
||||||
|
|
||||||
def display_cost_matrix(
|
|
||||||
Costs: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
path: List[Tuple[int, int]],
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
verb: VerboseMode,
|
|
||||||
) -> str:
|
|
||||||
'''
|
|
||||||
Zeigt Kostenmatrix + optimalen Pfad.
|
|
||||||
|
|
||||||
@inputs
|
|
||||||
- `Costs` - Kostenmatrix
|
|
||||||
- `Moves` - Kodiert die optimalen Schritte
|
|
||||||
- `X`, `Y` - Strings
|
|
||||||
|
|
||||||
@returns
|
|
||||||
- eine 'printable' Darstellung der Matrix mit den Strings X, Y + Indexes.
|
|
||||||
'''
|
|
||||||
table = represent_cost_matrix(Costs=Costs, path=path, X=X, Y=Y, verb=verb);
|
|
||||||
# benutze pandas-Dataframe + tabulate, um schöner darzustellen:
|
|
||||||
repr = tabulate(pd.DataFrame(table), showindex=False, stralign='center', tablefmt='plain');
|
|
||||||
return repr;
|
|
||||||
|
|
||||||
def display_cost_matrix_halves(
|
|
||||||
Costs1: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
Costs2: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
path1: List[Tuple[int, int]],
|
|
||||||
path2: List[Tuple[int, int]],
|
|
||||||
X1: str,
|
|
||||||
X2: str,
|
|
||||||
Y1: str,
|
|
||||||
Y2: str,
|
|
||||||
verb: VerboseMode,
|
|
||||||
) -> str:
|
|
||||||
'''
|
|
||||||
Zeigt Kostenmatrix + optimalen Pfad für Schritt im D & C Hirschberg-Algorithmus
|
|
||||||
|
|
||||||
@inputs
|
|
||||||
- `Costs1`, `Costs2` - Kostenmatrizen
|
|
||||||
- `Moves1`, `Moves2` - Kodiert die optimalen Schritte
|
|
||||||
- `X1`, `X2`, `Y1`, `Y2` - Strings
|
|
||||||
|
|
||||||
@returns
|
|
||||||
- eine 'printable' Darstellung der Matrix mit den Strings X, Y + Indexes.
|
|
||||||
'''
|
|
||||||
table1 = represent_cost_matrix(Costs=Costs1, path=path1, X=X1, Y=Y1, verb=verb, pad=True);
|
|
||||||
table2 = represent_cost_matrix(Costs=Costs2, path=path2, X=X2, Y=Y2, verb=verb, pad=True);
|
|
||||||
|
|
||||||
# merge Taellen:
|
|
||||||
table = np.concatenate([table1[:, :-1], table2[::-1, ::-1]], axis=1);
|
|
||||||
|
|
||||||
# benutze pandas-Dataframe + tabulate, um schöner darzustellen:
|
|
||||||
repr = tabulate(pd.DataFrame(table), showindex=False, stralign='center', tablefmt='plain');
|
|
||||||
return repr;
|
|
@ -1,127 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
from src.thirdparty.maths import *;
|
|
||||||
|
|
||||||
from src.hirschberg.constants import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'compute_cost_matrix',
|
|
||||||
'update_cost_matrix',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHODS cost matrix + optimal paths
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def compute_cost_matrix(
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
) -> Tuple[np.ndarray, np.ndarray]: # Tuple[NDArray[(Any, Any), int], NDArray[(Any, Any), Directions]]:
|
|
||||||
'''
|
|
||||||
Berechnet Hirschberg-Costs-Matrix (ohne Rekursion).
|
|
||||||
|
|
||||||
Annahmen:
|
|
||||||
- X[0] = gap
|
|
||||||
- Y[0] = gap
|
|
||||||
'''
|
|
||||||
m = len(X); # display vertically
|
|
||||||
n = len(Y); # display horizontally
|
|
||||||
Costs = np.full(shape=(m, n), dtype=int, fill_value=0);
|
|
||||||
Moves = np.full(shape=(m, n), dtype=Directions, fill_value=Directions.UNSET);
|
|
||||||
|
|
||||||
# zuerst 0. Spalte und 0. Zeile ausfüllen:
|
|
||||||
for i, x in list(enumerate(X))[1:]:
|
|
||||||
update_cost_matrix(Costs, Moves, x, '', i, 0);
|
|
||||||
|
|
||||||
for j, y in list(enumerate(Y))[1:]:
|
|
||||||
update_cost_matrix(Costs, Moves, '', y, 0, j);
|
|
||||||
|
|
||||||
# jetzt alle »inneren« Werte bestimmen:
|
|
||||||
for i, x in list(enumerate(X))[1:]:
|
|
||||||
for j, y in list(enumerate(Y))[1:]:
|
|
||||||
update_cost_matrix(Costs, Moves, x, y, i, j);
|
|
||||||
return Costs, Moves;
|
|
||||||
|
|
||||||
def update_cost_matrix(
|
|
||||||
Costs: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
Moves: np.ndarray, # NDArray[(Any, Any), Directions],
|
|
||||||
x: str,
|
|
||||||
y: str,
|
|
||||||
i: int,
|
|
||||||
j: int,
|
|
||||||
):
|
|
||||||
'''
|
|
||||||
Schrittweise Funktion zur Aktualisierung vom Eintrag `(i,j)` in der Kostenmatrix.
|
|
||||||
|
|
||||||
Annahme:
|
|
||||||
- alle »Vorgänger« von `(i,j)` in der Matrix sind bereits optimiert.
|
|
||||||
|
|
||||||
@inputs
|
|
||||||
- `Costs` - bisher berechnete Kostenmatrix
|
|
||||||
- `Moves` - bisher berechnete optimale Schritte
|
|
||||||
- `i`, `x` - Position und Wert in String `X` (»vertical« dargestellt)
|
|
||||||
- `j`, `y` - Position und Wert in String `Y` (»horizontal« dargestellt)
|
|
||||||
'''
|
|
||||||
|
|
||||||
# nichts zu tun, wenn (i, j) == (0, 0):
|
|
||||||
if i == 0 and j == 0:
|
|
||||||
Costs[0, 0] = 0;
|
|
||||||
return;
|
|
||||||
|
|
||||||
################################
|
|
||||||
# NOTE: Berechnung von möglichen Moves wie folgt.
|
|
||||||
#
|
|
||||||
# Fall 1: (i-1,j-1) ---> (i,j)
|
|
||||||
# ==> Stringvergleich ändert sich wie folgt:
|
|
||||||
# s1 s1 x
|
|
||||||
# ---- ---> ------
|
|
||||||
# s2 s2 y
|
|
||||||
#
|
|
||||||
# Fall 2: (i,j-1) ---> (i,j)
|
|
||||||
# ==> Stringvergleich ändert sich wie folgt:
|
|
||||||
# s1 s1 GAP
|
|
||||||
# ---- ---> -------
|
|
||||||
# s2 s2 y
|
|
||||||
#
|
|
||||||
# Fall 3: (i-1,j) ---> (i,j)
|
|
||||||
# ==> Stringvergleich ändert sich wie folgt:
|
|
||||||
# s1 s1 x
|
|
||||||
# ---- ---> -------
|
|
||||||
# s2 s2 GAP
|
|
||||||
#
|
|
||||||
# Diese Fälle berücksichtigen wir:
|
|
||||||
################################
|
|
||||||
edges = [];
|
|
||||||
if i > 0 and j > 0:
|
|
||||||
edges.append((
|
|
||||||
Directions.DIAGONAL,
|
|
||||||
Costs[i-1, j-1] + missmatch_penalty(x, y),
|
|
||||||
));
|
|
||||||
if j > 0:
|
|
||||||
edges.append((
|
|
||||||
Directions.HORIZONTAL,
|
|
||||||
Costs[i, j-1] + gap_penalty(y),
|
|
||||||
));
|
|
||||||
if i > 0:
|
|
||||||
edges.append((
|
|
||||||
Directions.VERTICAL,
|
|
||||||
Costs[i-1, j] + gap_penalty(x),
|
|
||||||
));
|
|
||||||
|
|
||||||
if len(edges) > 0:
|
|
||||||
# Sortiere nach Priorität (festgelegt in Enum):
|
|
||||||
edges = sorted(edges, key=lambda x: x[0].value);
|
|
||||||
# Wähle erste Möglichkeit mit minimalen Kosten:
|
|
||||||
index = np.argmin([ cost for _, cost in edges]);
|
|
||||||
Moves[i, j], Costs[i, j] = edges[index];
|
|
||||||
return;
|
|
@ -1,125 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
from src.thirdparty.maths import *;
|
|
||||||
|
|
||||||
from src.hirschberg.constants import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'get_optimal_transition',
|
|
||||||
'reconstruct_optimal_path',
|
|
||||||
'reconstruct_optimal_path_halves',
|
|
||||||
'reconstruct_words',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHODS optimaler treffpunkt
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def get_optimal_transition(
|
|
||||||
Costs1: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
Costs2: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
|
||||||
'''
|
|
||||||
Rekonstruiere »Treffpunkt«, wo die Gesamtkosten minimiert sind.
|
|
||||||
Dieser Punkt stellt einen optimal Übergang für den Rekursionsschritt dar.
|
|
||||||
'''
|
|
||||||
(m, n1) = Costs1.shape;
|
|
||||||
(m, n2) = Costs2.shape;
|
|
||||||
info = [
|
|
||||||
(
|
|
||||||
Costs1[i, n1-1] + Costs2[m-1-i, n2-1],
|
|
||||||
(i, n1-1),
|
|
||||||
(m-1-i, n2-1),
|
|
||||||
)
|
|
||||||
for i in range(m)
|
|
||||||
];
|
|
||||||
index = np.argmin([ cost for cost, _, _ in info ]);
|
|
||||||
coord1 = info[index][1];
|
|
||||||
coord2 = info[index][2];
|
|
||||||
return coord1, coord2;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHODS reconstruction von words/paths
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def reconstruct_optimal_path(
|
|
||||||
Moves: np.ndarray, # NDArray[(Any, Any), Directions],
|
|
||||||
coord: Optional[Tuple[int, int]] = None,
|
|
||||||
) -> List[Tuple[int, int]]:
|
|
||||||
'''
|
|
||||||
Liest Matrix mit optimalen Schritten den optimalen Pfad aus,
|
|
||||||
angenfangen von Endkoordinaten.
|
|
||||||
'''
|
|
||||||
if coord is None:
|
|
||||||
m, n = Moves.shape;
|
|
||||||
(i, j) = (m-1, n-1);
|
|
||||||
else:
|
|
||||||
(i, j) = coord;
|
|
||||||
path = [(i, j)];
|
|
||||||
while (i, j) != (0, 0):
|
|
||||||
match Moves[i, j]:
|
|
||||||
case Directions.DIAGONAL:
|
|
||||||
(i, j) = (i - 1, j - 1);
|
|
||||||
case Directions.HORIZONTAL:
|
|
||||||
(i, j) = (i, j - 1);
|
|
||||||
case Directions.VERTICAL:
|
|
||||||
(i, j) = (i - 1, j);
|
|
||||||
case _:
|
|
||||||
break;
|
|
||||||
path.append((i, j));
|
|
||||||
return path[::-1];
|
|
||||||
|
|
||||||
def reconstruct_optimal_path_halves(
|
|
||||||
Costs1: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
Costs2: np.ndarray, # NDArray[(Any, Any), int],
|
|
||||||
Moves1: np.ndarray, # NDArray[(Any, Any), Directions],
|
|
||||||
Moves2: np.ndarray, # NDArray[(Any, Any), Directions],
|
|
||||||
) -> Tuple[List[Tuple[int, int]], List[Tuple[int, int]]]:
|
|
||||||
'''
|
|
||||||
Rekonstruiere optimale Pfad für Rekursionsschritt,
|
|
||||||
wenn horizontales Wort in 2 aufgeteilt wird.
|
|
||||||
'''
|
|
||||||
coord1, coord2 = get_optimal_transition(Costs1=Costs1, Costs2=Costs2);
|
|
||||||
path1 = reconstruct_optimal_path(Moves1, coord=coord1);
|
|
||||||
path2 = reconstruct_optimal_path(Moves2, coord=coord2);
|
|
||||||
return path1, path2;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# METHODS reconstruction von words
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
def reconstruct_words(
|
|
||||||
X: str,
|
|
||||||
Y: str,
|
|
||||||
moves: List[Directions],
|
|
||||||
path: List[Tuple[int, int]],
|
|
||||||
) -> Tuple[str, str]:
|
|
||||||
'''
|
|
||||||
Berechnet String-Alignment aus Path.
|
|
||||||
'''
|
|
||||||
word_x = '';
|
|
||||||
word_y = '';
|
|
||||||
for ((i, j), move) in zip(path, moves):
|
|
||||||
x = X[i];
|
|
||||||
y = Y[j];
|
|
||||||
match move:
|
|
||||||
case Directions.DIAGONAL:
|
|
||||||
word_x += x;
|
|
||||||
word_y += y;
|
|
||||||
case Directions.HORIZONTAL:
|
|
||||||
word_x += '-';
|
|
||||||
word_y += y;
|
|
||||||
case Directions.VERTICAL:
|
|
||||||
word_x += x;
|
|
||||||
word_y += '-';
|
|
||||||
return word_x, word_y;
|
|
@ -1,107 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# IMPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
from __future__ import annotations;
|
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
|
||||||
from src.thirdparty.maths import *;
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# EXPORTS
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'Alignment',
|
|
||||||
'AlignmentBasic',
|
|
||||||
'AlignmentPair',
|
|
||||||
];
|
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
# Class Alignments
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
class Alignment():
|
|
||||||
@property
|
|
||||||
def parts1(self) -> List[str]:
|
|
||||||
if isinstance(self, AlignmentBasic):
|
|
||||||
return [self.word1];
|
|
||||||
elif isinstance(self, AlignmentPair):
|
|
||||||
return self.left.parts1 + self.right.parts1;
|
|
||||||
return [];
|
|
||||||
|
|
||||||
@property
|
|
||||||
def parts2(self) -> List[str]:
|
|
||||||
if isinstance(self, AlignmentBasic):
|
|
||||||
return [self.word2];
|
|
||||||
elif isinstance(self, AlignmentPair):
|
|
||||||
return self.left.parts2 + self.right.parts2;
|
|
||||||
return [];
|
|
||||||
|
|
||||||
def astree(
|
|
||||||
self,
|
|
||||||
indent: str = ' ',
|
|
||||||
prefix: str = '',
|
|
||||||
braces: bool = False,
|
|
||||||
branch: str = '|____ ',
|
|
||||||
) -> str:
|
|
||||||
return '\n'.join(list(self._astree_recursion(indent=indent, prefix=prefix, braces=braces, branch=branch)));
|
|
||||||
|
|
||||||
def _astree_recursion(
|
|
||||||
self,
|
|
||||||
depth: int = 0,
|
|
||||||
indent: str = ' ',
|
|
||||||
prefix: str = '',
|
|
||||||
braces: bool = False,
|
|
||||||
branch: str = '|____ ',
|
|
||||||
) -> Generator[str, None, None]:
|
|
||||||
word1 = self.as_string1(braces=braces);
|
|
||||||
word2 = self.as_string2(braces=braces);
|
|
||||||
if isinstance(self, AlignmentBasic):
|
|
||||||
u = prefix + branch if depth > 0 else prefix;
|
|
||||||
yield f'{u}{word2}';
|
|
||||||
yield f'{" "*len(u)}{word1}';
|
|
||||||
elif isinstance(self, AlignmentPair):
|
|
||||||
u = prefix + branch if depth > 0 else prefix;
|
|
||||||
yield f'{u}{word2}';
|
|
||||||
yield f'{" "*len(u)}{word1}';
|
|
||||||
yield '';
|
|
||||||
yield from self.left._astree_recursion(
|
|
||||||
depth = depth + 1,
|
|
||||||
indent = indent,
|
|
||||||
prefix = indent + prefix,
|
|
||||||
braces = braces,
|
|
||||||
branch = branch,
|
|
||||||
);
|
|
||||||
yield '';
|
|
||||||
yield from self.right._astree_recursion(
|
|
||||||
depth = depth + 1,
|
|
||||||
indent = indent,
|
|
||||||
prefix = indent + prefix,
|
|
||||||
braces = braces,
|
|
||||||
branch = branch,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
|
|
||||||
def as_string1(self, braces: bool = False) -> Tuple[str, str]:
|
|
||||||
if braces:
|
|
||||||
return f'({")(".join(self.parts1)})';
|
|
||||||
return ''.join(self.parts1);
|
|
||||||
|
|
||||||
def as_string2(self, braces: bool = False,) -> Tuple[str, str]:
|
|
||||||
if braces:
|
|
||||||
return f'({")(".join(self.parts2)})';
|
|
||||||
return ''.join(self.parts2);
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AlignmentBasic(Alignment):
|
|
||||||
word1: str = field();
|
|
||||||
word2: str = field();
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class AlignmentPair(Alignment):
|
|
||||||
left: Alignment = field();
|
|
||||||
right: Alignment = field();
|
|
@ -5,8 +5,6 @@
|
|||||||
# IMPORTS
|
# IMPORTS
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
from dataclasses import dataclass;
|
|
||||||
from dataclasses import field;
|
|
||||||
from enum import Enum;
|
from enum import Enum;
|
||||||
from types import TracebackType;
|
from types import TracebackType;
|
||||||
from typing import Any;
|
from typing import Any;
|
||||||
@ -27,8 +25,6 @@ from nptyping import NDArray;
|
|||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'dataclass',
|
|
||||||
'field',
|
|
||||||
'Enum',
|
'Enum',
|
||||||
'TracebackType',
|
'TracebackType',
|
||||||
'Any',
|
'Any',
|
@ -12,11 +12,11 @@ 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.core.log import *;
|
||||||
from src.thirdparty.maths import *;
|
from src.local.maths 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.travel.naive import *;
|
||||||
from src.hirschberg import *;
|
from src.string_alignment.hirschberg import *;
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# GLOBAL CONSTANTS/VARIABLES
|
# GLOBAL CONSTANTS/VARIABLES
|
||||||
@ -41,7 +41,8 @@ def enter():
|
|||||||
# verbose=True,
|
# verbose=True,
|
||||||
# );
|
# );
|
||||||
## Beispiel für Seminarwoche 10 (Blatt 9):
|
## Beispiel für Seminarwoche 10 (Blatt 9):
|
||||||
hirschberg_algorithm(
|
hirschberg_algorithm_once(
|
||||||
|
# hirschberg_algorithm(
|
||||||
# Y = 'ANSPANNEN',
|
# Y = 'ANSPANNEN',
|
||||||
# X = 'ANSTRENGEN',
|
# X = 'ANSTRENGEN',
|
||||||
# Y = 'AGAT',
|
# Y = 'AGAT',
|
||||||
@ -50,14 +51,9 @@ def enter():
|
|||||||
X = 'happily ever, lol',
|
X = 'happily ever, lol',
|
||||||
# Y = 'apple',
|
# Y = 'apple',
|
||||||
# X = 'happily',
|
# X = 'happily',
|
||||||
once = True,
|
mode = DisplayMode.COSTS,
|
||||||
verb = VerboseMode.COSTS,
|
# mode = DisplayMode.MOVES,
|
||||||
# verb = VerboseMode.MOVES,
|
# mode = DisplayMode.COSTS_AND_MOVES,
|
||||||
# verb = VerboseMode.COSTS_AND_MOVES,
|
|
||||||
show = [
|
|
||||||
# DisplayOptions.ATOMS,
|
|
||||||
# DisplayOptions.TREE,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
@ -5,7 +5,7 @@
|
|||||||
# IMPORTS
|
# IMPORTS
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
from src.local.typing import *;
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# EXPORTS
|
# EXPORTS
|
||||||
|
453
code/python/src/string_alignment/hirschberg.py
Normal file
453
code/python/src/string_alignment/hirschberg.py
Normal file
@ -0,0 +1,453 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# IMPORTS
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
from __future__ import annotations;
|
||||||
|
from src.local.typing import *;
|
||||||
|
from src.local.maths import *;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# EXPORTS
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'hirschberg_algorithm',
|
||||||
|
'hirschberg_algorithm_once',
|
||||||
|
'DisplayMode'
|
||||||
|
];
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# CONSTANTS / SETUP
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
class DisplayMode(Enum):
|
||||||
|
NONE = -1;
|
||||||
|
COSTS = 0;
|
||||||
|
MOVES = 1;
|
||||||
|
COSTS_AND_MOVES = 2;
|
||||||
|
|
||||||
|
class Directions(Enum):
|
||||||
|
UNSET = -1;
|
||||||
|
# Prioritäten hier setzen
|
||||||
|
DIAGONAL = 0;
|
||||||
|
HORIZONTAL = 1;
|
||||||
|
VERTICAL = 2;
|
||||||
|
|
||||||
|
def gap_penalty(x: str):
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
def missmatch_penalty(x: str, y: str):
|
||||||
|
return 0 if x == y else 1;
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# METHOD hirschberg_algorithm
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
def hirschberg_algorithm_once(
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
mode: DisplayMode = DisplayMode.NONE,
|
||||||
|
) -> Tuple[str, str]:
|
||||||
|
Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y);
|
||||||
|
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);
|
||||||
|
if mode != DisplayMode.NONE:
|
||||||
|
repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y, mode=mode);
|
||||||
|
print(f'\n{repr}');
|
||||||
|
print(f'\n\x1b[1mOptimales Alignment:\x1b[0m');
|
||||||
|
print('');
|
||||||
|
print(word_y);
|
||||||
|
print(len(word_x) * '-');
|
||||||
|
print(word_x);
|
||||||
|
print('');
|
||||||
|
return word_x, word_y;
|
||||||
|
|
||||||
|
def hirschberg_algorithm(
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
mode: DisplayMode = DisplayMode.NONE,
|
||||||
|
) -> Tuple[str, str]:
|
||||||
|
alignments_x, alignments_y = hirschberg_algorithm_step(X=X, Y=Y, depth=1, mode=mode);
|
||||||
|
word_x = ''.join(alignments_x);
|
||||||
|
word_y = ''.join(alignments_y);
|
||||||
|
if mode != DisplayMode.NONE:
|
||||||
|
display_x = f'[{"][".join(alignments_x)}]';
|
||||||
|
display_y = f'[{"][".join(alignments_y)}]';
|
||||||
|
print(f'\n\x1b[1mOptimales Alignment:\x1b[0m');
|
||||||
|
print('');
|
||||||
|
print(display_y);
|
||||||
|
print(len(display_x) * '-');
|
||||||
|
print(display_x);
|
||||||
|
print('');
|
||||||
|
return word_x, word_y;
|
||||||
|
|
||||||
|
def hirschberg_algorithm_step(
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
depth: int = 0,
|
||||||
|
mode: DisplayMode = DisplayMode.NONE,
|
||||||
|
) -> Tuple[List[str], List[str]]:
|
||||||
|
n = len(Y);
|
||||||
|
if n == 1:
|
||||||
|
Costs, Moves = compute_cost_matrix(X = '-' + X, Y = '-' + Y);
|
||||||
|
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);
|
||||||
|
# if verbose:
|
||||||
|
# repr = display_cost_matrix(Costs=Costs, path=path, X = '-' + X, Y = '-' + Y);
|
||||||
|
# print(f'\n\x1b[1mRekursionstiefe: {depth}\x1b[0m\n\n{repr}')
|
||||||
|
return [word_x], [word_y];
|
||||||
|
else:
|
||||||
|
n = int(np.ceil(n/2));
|
||||||
|
|
||||||
|
# bilde linke Hälfte vom horizontalen Wort:
|
||||||
|
Y1 = Y[:n];
|
||||||
|
X1 = X;
|
||||||
|
|
||||||
|
# bilde rechte Hälfte vom horizontalen Wort (und kehre h. + v. um):
|
||||||
|
Y2 = Y[n:][::-1];
|
||||||
|
X2 = X[::-1];
|
||||||
|
|
||||||
|
# Löse Teilprobleme:
|
||||||
|
Costs1, Moves1 = compute_cost_matrix(X = '-' + X1, Y = '-' + Y1);
|
||||||
|
Costs2, Moves2 = compute_cost_matrix(X = '-' + X2, Y = '-' + Y2);
|
||||||
|
|
||||||
|
if mode != DisplayMode.NONE:
|
||||||
|
path1, path2 = reconstruct_optimal_path_halves(Costs1=Costs1, Costs2=Costs2, Moves1=Moves1, Moves2=Moves2);
|
||||||
|
repr = display_cost_matrix_halves(
|
||||||
|
Costs1 = Costs1,
|
||||||
|
Costs2 = Costs2,
|
||||||
|
path1 = path1,
|
||||||
|
path2 = path2,
|
||||||
|
X1 = '-' + X1,
|
||||||
|
X2 = '-' + X2,
|
||||||
|
Y1 = '-' + Y1,
|
||||||
|
Y2 = '-' + Y2,
|
||||||
|
mode = mode,
|
||||||
|
);
|
||||||
|
print(f'\n\x1b[1mRekursionstiefe: {depth}\x1b[0m\n\n{repr}')
|
||||||
|
|
||||||
|
# Koordinaten des optimalen Übergangs berechnen:
|
||||||
|
coord1, coord2 = get_optimal_transition(Costs1=Costs1, Costs2=Costs2);
|
||||||
|
p = coord1[0];
|
||||||
|
# Divide and Conquer ausführen:
|
||||||
|
alignments_x_1, alignments_y_1 = hirschberg_algorithm_step(X=X[:p], Y=Y[:n], depth=depth+1, verbose=verbose, mode=mode);
|
||||||
|
alignments_x_2, alignments_y_2 = hirschberg_algorithm_step(X=X[p:], Y=Y[n:], depth=depth+1, verbose=verbose, mode=mode);
|
||||||
|
|
||||||
|
# Resultate zusammensetzen:
|
||||||
|
alignments_x = alignments_x_1 + alignments_x_2;
|
||||||
|
alignments_y = alignments_y_1 + alignments_y_2;
|
||||||
|
if len(Y[:n]) <= 1 and len(Y[n:]) <= 1:
|
||||||
|
# falls linke + rechte Hälfte nur aus <= 1 Buchstsaben bestehen, bestehen Alignment aus nur einem Teil ---> führe zusammen:
|
||||||
|
alignments_x = [ ''.join(alignments_x) ];
|
||||||
|
alignments_y = [ ''.join(alignments_y) ];
|
||||||
|
return alignments_x, alignments_y;
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# METHODS cost matrix + optimal paths
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
def compute_cost_matrix(
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
) -> Tuple[NDArray[(Any, Any), int], NDArray[(Any, Any), Directions]]:
|
||||||
|
'''
|
||||||
|
Berechnet Hirschberg-Costs-Matrix (ohne Rekursion).
|
||||||
|
|
||||||
|
Annahmen:
|
||||||
|
- X[0] = gap
|
||||||
|
- Y[0] = gap
|
||||||
|
'''
|
||||||
|
m = len(X); # display vertically
|
||||||
|
n = len(Y); # display horizontally
|
||||||
|
Costs = np.full(shape=(m, n), dtype=int, fill_value=0);
|
||||||
|
Moves = np.full(shape=(m, n), dtype=Directions, fill_value=Directions.UNSET);
|
||||||
|
|
||||||
|
# zuerst 0. Spalte und 0. Zeile ausfüllen:
|
||||||
|
for i, x in list(enumerate(X))[1:]:
|
||||||
|
update_cost_matrix(Costs, Moves, x, '', i, 0);
|
||||||
|
|
||||||
|
for j, y in list(enumerate(Y))[1:]:
|
||||||
|
update_cost_matrix(Costs, Moves, '', y, 0, j);
|
||||||
|
|
||||||
|
# jetzt alle »inneren« Werte bestimmen:
|
||||||
|
for i, x in list(enumerate(X))[1:]:
|
||||||
|
for j, y in list(enumerate(Y))[1:]:
|
||||||
|
update_cost_matrix(Costs, Moves, x, y, i, j);
|
||||||
|
return Costs, Moves;
|
||||||
|
|
||||||
|
def update_cost_matrix(
|
||||||
|
Costs: NDArray[(Any, Any), int],
|
||||||
|
Moves: NDArray[(Any, Any), Directions],
|
||||||
|
x: str,
|
||||||
|
y: str,
|
||||||
|
i: int,
|
||||||
|
j: int,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Schrittweise Funktion zur Aktualisierung vom Eintrag `(i,j)` in der Kostenmatrix.
|
||||||
|
|
||||||
|
Annahme:
|
||||||
|
- alle »Vorgänger« von `(i,j)` in der Matrix sind bereits optimiert.
|
||||||
|
|
||||||
|
@inputs
|
||||||
|
- `Costs` - bisher berechnete Kostenmatrix
|
||||||
|
- `Moves` - bisher berechnete optimale Schritte
|
||||||
|
- `i`, `x` - Position und Wert in String `X` (»vertical« dargestellt)
|
||||||
|
- `j`, `y` - Position und Wert in String `Y` (»horizontal« dargestellt)
|
||||||
|
'''
|
||||||
|
|
||||||
|
# nichts zu tun, wenn (i, j) == (0, 0):
|
||||||
|
if i == 0 and j == 0:
|
||||||
|
Costs[0, 0] = 0;
|
||||||
|
return;
|
||||||
|
|
||||||
|
################################
|
||||||
|
# NOTE: Berechnung von möglichen Moves wie folgt.
|
||||||
|
#
|
||||||
|
# Fall 1: (i-1,j-1) ---> (i,j)
|
||||||
|
# ==> Stringvergleich ändert sich wie folgt:
|
||||||
|
# s1 s1 x
|
||||||
|
# ---- ---> ------
|
||||||
|
# s2 s2 y
|
||||||
|
#
|
||||||
|
# Fall 2: (i,j-1) ---> (i,j)
|
||||||
|
# ==> Stringvergleich ändert sich wie folgt:
|
||||||
|
# s1 s1 GAP
|
||||||
|
# ---- ---> -------
|
||||||
|
# s2 s2 y
|
||||||
|
#
|
||||||
|
# Fall 3: (i-1,j) ---> (i,j)
|
||||||
|
# ==> Stringvergleich ändert sich wie folgt:
|
||||||
|
# s1 s1 x
|
||||||
|
# ---- ---> -------
|
||||||
|
# s2 s2 GAP
|
||||||
|
#
|
||||||
|
# Diese Fälle berücksichtigen wir:
|
||||||
|
################################
|
||||||
|
edges = [];
|
||||||
|
if i > 0 and j > 0:
|
||||||
|
edges.append((
|
||||||
|
Directions.DIAGONAL,
|
||||||
|
Costs[i-1, j-1] + missmatch_penalty(x, y),
|
||||||
|
));
|
||||||
|
if j > 0:
|
||||||
|
edges.append((
|
||||||
|
Directions.HORIZONTAL,
|
||||||
|
Costs[i, j-1] + gap_penalty(y),
|
||||||
|
));
|
||||||
|
if i > 0:
|
||||||
|
edges.append((
|
||||||
|
Directions.VERTICAL,
|
||||||
|
Costs[i-1, j] + gap_penalty(x),
|
||||||
|
));
|
||||||
|
|
||||||
|
if len(edges) > 0:
|
||||||
|
# Sortiere nach Priorität (festgelegt in Enum):
|
||||||
|
edges = sorted(edges, key=lambda x: x[0].value);
|
||||||
|
# Wähle erste Möglichkeit mit minimalen Kosten:
|
||||||
|
index = np.argmin([ cost for _, cost in edges]);
|
||||||
|
Moves[i, j], Costs[i, j] = edges[index];
|
||||||
|
return;
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# METHODS optimaler treffpunkt
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
def get_optimal_transition(
|
||||||
|
Costs1: NDArray[(Any, Any), int],
|
||||||
|
Costs2: NDArray[(Any, Any), int],
|
||||||
|
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
||||||
|
'''
|
||||||
|
Rekonstruiere »Treffpunkt«, wo die Gesamtkosten minimiert sind.
|
||||||
|
Dieser Punkt stellt einen optimal Übergang für den Rekursionsschritt dar.
|
||||||
|
'''
|
||||||
|
(m, n1) = Costs1.shape;
|
||||||
|
(m, n2) = Costs2.shape;
|
||||||
|
info = [
|
||||||
|
(
|
||||||
|
Costs1[i, n1-1] + Costs2[m-1-i, n2-1],
|
||||||
|
(i, n1-1),
|
||||||
|
(m-1-i, n2-1),
|
||||||
|
)
|
||||||
|
for i in range(m)
|
||||||
|
];
|
||||||
|
index = np.argmin([ cost for cost, _, _ in info ]);
|
||||||
|
coord1 = info[index][1];
|
||||||
|
coord2 = info[index][2];
|
||||||
|
return coord1, coord2;
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# METHODS reconstruction von words/paths
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
def reconstruct_optimal_path(
|
||||||
|
Moves: NDArray[(Any, Any), Directions],
|
||||||
|
coord: Optional[Tuple[int, int]] = None,
|
||||||
|
) -> List[Tuple[int, int]]:
|
||||||
|
'''
|
||||||
|
Liest Matrix mit optimalen Schritten den optimalen Pfad aus,
|
||||||
|
angenfangen von Endkoordinaten.
|
||||||
|
'''
|
||||||
|
if coord is None:
|
||||||
|
m, n = Moves.shape;
|
||||||
|
(i, j) = (m-1, n-1);
|
||||||
|
else:
|
||||||
|
(i, j) = coord;
|
||||||
|
path = [(i, j)];
|
||||||
|
while (i, j) != (0, 0):
|
||||||
|
match Moves[i, j]:
|
||||||
|
case Directions.DIAGONAL:
|
||||||
|
(i, j) = (i - 1, j - 1);
|
||||||
|
case Directions.HORIZONTAL:
|
||||||
|
(i, j) = (i, j - 1);
|
||||||
|
case Directions.VERTICAL:
|
||||||
|
(i, j) = (i - 1, j);
|
||||||
|
case _:
|
||||||
|
break;
|
||||||
|
path.append((i, j));
|
||||||
|
return path[::-1];
|
||||||
|
|
||||||
|
def reconstruct_optimal_path_halves(
|
||||||
|
Costs1: NDArray[(Any, Any), int],
|
||||||
|
Costs2: NDArray[(Any, Any), int],
|
||||||
|
Moves1: NDArray[(Any, Any), Directions],
|
||||||
|
Moves2: NDArray[(Any, Any), Directions],
|
||||||
|
) -> Tuple[List[Tuple[int, int]], List[Tuple[int, int]]]:
|
||||||
|
'''
|
||||||
|
Rekonstruiere optimale Pfad für Rekursionsschritt,
|
||||||
|
wenn horizontales Wort in 2 aufgeteilt wird.
|
||||||
|
'''
|
||||||
|
coord1, coord2 = get_optimal_transition(Costs1=Costs1, Costs2=Costs2);
|
||||||
|
path1 = reconstruct_optimal_path(Moves1, coord=coord1);
|
||||||
|
path2 = reconstruct_optimal_path(Moves2, coord=coord2);
|
||||||
|
return path1, path2;
|
||||||
|
|
||||||
|
def reconstruct_words(
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
moves: List[Directions],
|
||||||
|
path: List[Tuple[int, int]],
|
||||||
|
) -> Tuple[str, str]:
|
||||||
|
word_x = '';
|
||||||
|
word_y = '';
|
||||||
|
for ((i, j), move) in zip(path, moves):
|
||||||
|
x = X[i];
|
||||||
|
y = Y[j];
|
||||||
|
match move:
|
||||||
|
case Directions.DIAGONAL:
|
||||||
|
word_x += x;
|
||||||
|
word_y += y;
|
||||||
|
case Directions.HORIZONTAL:
|
||||||
|
word_x += '-';
|
||||||
|
word_y += y;
|
||||||
|
case Directions.VERTICAL:
|
||||||
|
word_x += x;
|
||||||
|
word_y += '-';
|
||||||
|
return word_x, word_y;
|
||||||
|
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
# AUXILIARY METHODS
|
||||||
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
def represent_cost_matrix(
|
||||||
|
Costs: NDArray[(Any, Any), int],
|
||||||
|
path: List[Tuple[int, int]],
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
mode: DisplayMode,
|
||||||
|
pad: bool = False,
|
||||||
|
) -> NDArray[(Any, Any), Any]:
|
||||||
|
m = len(X); # display vertically
|
||||||
|
n = len(Y); # display horizontally
|
||||||
|
|
||||||
|
# erstelle string-Array:
|
||||||
|
if pad:
|
||||||
|
table = np.full(shape=(3 + m + 3, 3 + n + 1), dtype=object, fill_value='');
|
||||||
|
else:
|
||||||
|
table = np.full(shape=(3 + m, 3 + n), dtype=object, fill_value='');
|
||||||
|
|
||||||
|
# topmost rows:
|
||||||
|
table[0, 3:(3+n)] = [str(j) for j in range(n)];
|
||||||
|
table[1, 3:(3+n)] = [y for y in Y];
|
||||||
|
table[2, 3:(3+n)] = '--';
|
||||||
|
# leftmost columns:
|
||||||
|
table[3:(3+m), 0] = [str(i) for i in range(m)];
|
||||||
|
table[3:(3+m), 1] = [x for x in X];
|
||||||
|
table[3:(3+m), 2] = '|';
|
||||||
|
|
||||||
|
if pad:
|
||||||
|
table[-3, 3:(3+n)] = '--';
|
||||||
|
table[3:(3+m), -1] = '|';
|
||||||
|
|
||||||
|
match mode:
|
||||||
|
case DisplayMode.MOVES:
|
||||||
|
table[3:(3+m), 3:(3+n)] = '.';
|
||||||
|
for (i, j) in path:
|
||||||
|
table[3 + i, 3 + j] = '*';
|
||||||
|
case DisplayMode.COSTS | DisplayMode.COSTS_AND_MOVES:
|
||||||
|
table[3:(3+m), 3:(3+n)] = Costs.copy();
|
||||||
|
if mode == DisplayMode.COSTS_AND_MOVES:
|
||||||
|
for (i, j) in path:
|
||||||
|
table[3 + i, 3 + j] = f'{{{table[3 + i, 3 + j]}}}';
|
||||||
|
|
||||||
|
return table;
|
||||||
|
|
||||||
|
def display_cost_matrix(
|
||||||
|
Costs: NDArray[(Any, Any), int],
|
||||||
|
path: List[Tuple[int, int]],
|
||||||
|
X: str,
|
||||||
|
Y: str,
|
||||||
|
mode: DisplayMode,
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
Zeigt Kostenmatrix + optimalen Pfad.
|
||||||
|
|
||||||
|
@inputs
|
||||||
|
- `Costs` - Kostenmatrix
|
||||||
|
- `Moves` - Kodiert die optimalen Schritte
|
||||||
|
- `X`, `Y` - Strings
|
||||||
|
|
||||||
|
@returns
|
||||||
|
- eine 'printable' Darstellung der Matrix mit den Strings X, Y + Indexes.
|
||||||
|
'''
|
||||||
|
table = represent_cost_matrix(Costs=Costs, path=path, X=X, Y=Y, mode=mode);
|
||||||
|
# benutze pandas-Dataframe + tabulate, um schöner darzustellen:
|
||||||
|
repr = tabulate(pd.DataFrame(table), showindex=False, stralign='center', tablefmt='plain');
|
||||||
|
return repr;
|
||||||
|
|
||||||
|
def display_cost_matrix_halves(
|
||||||
|
Costs1: NDArray[(Any, Any), int],
|
||||||
|
Costs2: NDArray[(Any, Any), int],
|
||||||
|
path1: List[Tuple[int, int]],
|
||||||
|
path2: List[Tuple[int, int]],
|
||||||
|
X1: str,
|
||||||
|
X2: str,
|
||||||
|
Y1: str,
|
||||||
|
Y2: str,
|
||||||
|
mode: DisplayMode,
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
Zeigt Kostenmatrix + optimalen Pfad für Schritt im D & C Hirschberg-Algorithmus
|
||||||
|
|
||||||
|
@inputs
|
||||||
|
- `Costs1`, `Costs2` - Kostenmatrizen
|
||||||
|
- `Moves1`, `Moves2` - Kodiert die optimalen Schritte
|
||||||
|
- `X1`, `X2`, `Y1`, `Y2` - Strings
|
||||||
|
|
||||||
|
@returns
|
||||||
|
- eine 'printable' Darstellung der Matrix mit den Strings X, Y + Indexes.
|
||||||
|
'''
|
||||||
|
table1 = represent_cost_matrix(Costs=Costs1, path=path1, X=X1, Y=Y1, mode=mode, pad=True);
|
||||||
|
table2 = represent_cost_matrix(Costs=Costs2, path=path2, X=X2, Y=Y2, mode=mode, pad=True);
|
||||||
|
|
||||||
|
# merge Taellen:
|
||||||
|
table = np.concatenate([table1[:, :-1], table2[::-1, ::-1]], axis=1);
|
||||||
|
|
||||||
|
# benutze pandas-Dataframe + tabulate, um schöner darzustellen:
|
||||||
|
repr = tabulate(pd.DataFrame(table), showindex=False, stralign='center', tablefmt='plain');
|
||||||
|
return repr;
|
@ -6,8 +6,8 @@
|
|||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
from __future__ import annotations;
|
from __future__ import annotations;
|
||||||
from src.thirdparty.types import *;
|
from src.local.typing import *;
|
||||||
from src.thirdparty.maths import *;
|
from src.local.maths import *;
|
||||||
|
|
||||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
# EXPORTS
|
# EXPORTS
|
||||||
|
@ -12,7 +12,7 @@ from pytest import lazy_fixture;
|
|||||||
from unittest import TestCase;
|
from unittest import TestCase;
|
||||||
from unittest.mock import patch;
|
from unittest.mock import patch;
|
||||||
|
|
||||||
from src.thirdparty.types import *;
|
from src.local.typing import *;
|
||||||
from src.graphs.graph import *;
|
from src.graphs.graph import *;
|
||||||
from src.graphs.tarjan import *;
|
from src.graphs.tarjan import *;
|
||||||
|
|
0
code/python/tests/test_stacks/__init__.py
Normal file
0
code/python/tests/test_stacks/__init__.py
Normal file
Loading…
x
Reference in New Issue
Block a user