master > master: code py - refactoring

This commit is contained in:
RD
2022-06-10 12:28:00 +02:00
parent 0116e2fbfb
commit b4a5ee213c
25 changed files with 126 additions and 40 deletions

View File

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.algorithms.hirschberg.algorithms import *;
from src.models.hirschberg.penalties import *;
from src.algorithms.hirschberg.display import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'hirschberg_algorithm',
];

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
from models.generated.config import *;
from src.models.hirschberg.penalties import *;
from src.algorithms.hirschberg.display import *;
from src.algorithms.hirschberg.matrix import *;
from src.algorithms.hirschberg.paths import *;
from src.models.hirschberg.alignment import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'hirschberg_algorithm',
'simple_algorithm',
];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHOD hirschberg_algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def simple_algorithm(
X: str,
Y: str,
verb: List[EnumHirschbergVerbosity] = [],
show: List[EnumHirschbergShow] = [],
) -> 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 != []:
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: List[EnumHirschbergVerbosity] = [],
show: List[EnumHirschbergShow] = [],
) -> 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 != []:
if EnumHirschbergShow.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: List[EnumHirschbergVerbosity] = [],
show: List[EnumHirschbergShow] = [],
) -> 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 != [] and (EnumHirschbergShow.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 != []:
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);

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
from models.generated.config import *;
from src.models.hirschberg.penalties 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: List[EnumHirschbergVerbosity],
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)] = [ f'\x1b[2m{j}\x1b[0m' for j in range(n) ];
table[1, 3:(3+n)] = [ f'\x1b[1m{y}\x1b[0m' for y in Y ];
table[2, 3:(3+n)] = '--';
# leftmost columns:
table[3:(3+m), 0] = [ f'\x1b[2m{i}\x1b[0m' for i in range(m) ];
table[3:(3+m), 1] = [ f'\x1b[1m{x}\x1b[0m' for x in X ];
table[3:(3+m), 2] = '|';
if pad:
table[-3, 3:(3+n)] = '--';
table[3:(3+m), -1] = '|';
if EnumHirschbergVerbosity.costs in verb:
table[3:(3+m), 3:(3+n)] = Costs.copy();
if EnumHirschbergVerbosity.moves in verb:
for (i, j) in path:
table[3 + i, 3 + j] = f'\x1b[31;4;1m{table[3 + i, 3 + j]}\x1b[0m';
elif EnumHirschbergVerbosity.moves in verb:
table[3:(3+m), 3:(3+n)] = '\x1b[2m.\x1b[0m';
for (i, j) in path:
table[3 + i, 3 + j] = '\x1b[31;1m*\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: EnumHirschbergVerbosity,
) -> 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: EnumHirschbergVerbosity,
) -> 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;

View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
from src.models.hirschberg 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;

View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
from src.models.hirschberg 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;

View File

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

View File

@@ -0,0 +1,167 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations;
from src.thirdparty.types import *;
from src.core.log import *;
from src.models.stacks import *;
from src.models.graphs import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'tarjan_algorithm',
];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class State(Enum):
UNTOUCHED = 0;
PENDING = 1;
FINISHED = 2;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Tarjan Algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def tarjan_algorithm(G: Graph, debug: bool = False) -> List[Any]:
'''
# Tarjan Algorithm #
Runs the Tarjan-Algorithm to compute the strongly connected components.
'''
# initialise state - mark all nodes as UNTOUCHED:
ctx = Context(G, debug=debug);
# loop through all nodes and carry out Tarjan-Algorithm, provided node not already visitted.
for u in G.nodes:
if ctx.get_state(u) == State.UNTOUCHED:
tarjan_visit(G, u, ctx);
return ctx.components;
def tarjan_visit(G: Graph, u: Any, ctx: Context):
'''
Recursive depth-first search algorithm to compute strongly components of a graph.
'''
# Place node on stack + initialise visit-index + component-index.
ctx.max_index += 1;
ctx.push(u);
ctx.set_least_index(u, ctx.max_index);
ctx.set_index(u, ctx.max_index);
ctx.set_state(u, State.PENDING);
'''
Compute strongly connected components of each child node.
NOTE: Child nodes remain on stack, if and only if parent is in same component.
'''
for v in G.successors(u):
# Visit child node only if untouched:
if ctx.get_state(v) == State.UNTOUCHED:
tarjan_visit(G, v, ctx);
ctx.set_least_index(u, min(ctx.get_least_index(u), ctx.get_least_index(v)));
# Otherwise update associated component-index of parent node, if in same component as child:
elif ctx.stack_contains(v):
ctx.set_least_index(u, min(ctx.get_least_index(u), ctx.get_index(v)));
ctx.set_state(u, State.FINISHED);
ctx.log_info(u);
# If at least-index of component pop everything from stack up to least index and add component:
if ctx.get_index(u) == ctx.get_least_index(u):
component = [];
while True:
v = ctx.top();
ctx.pop();
component.append(v);
if u == v:
break;
ctx.components.append(component);
return;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY context variables for algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@dataclass
class NodeInformationDefault:
node: Any = field(default=None);
least_index: int = field(default=0);
index: int = field(default=0);
state: State = field(default=State.UNTOUCHED, repr=False);
class NodeInformation(NodeInformationDefault):
def __init__(self, u: Any):
super().__init__();
self.node = u;
@dataclass
class ContextDefault:
max_index: int = field(default=0);
debug: bool = field(default=False);
stack: Stack = field(default_factory=lambda: Stack());
components: list[list[Any]] = field(default_factory=lambda: []);
infos: dict[Any, NodeInformation] = field(default_factory=lambda: dict());
class Context(ContextDefault):
def __init__(self, G: Graph, debug: bool):
super().__init__();
self.debug = debug;
self.infos = { u: NodeInformation(u) for u in G.nodes };
def push(self, u: Any):
self.stack.push(u);
def top(self) -> Any:
return self.stack.top();
def pop(self) -> Any:
return self.stack.pop();
def update_infos(self, u: Any, info: NodeInformation):
self.infos[u] = info;
def set_state(self, u: Any, state: State):
info = self.infos[u];
info.state = state;
self.update_infos(u, info);
def set_least_index(self, u: Any, least_index: int):
info = self.infos[u];
info.least_index = least_index;
self.update_infos(u, info);
def set_index(self, u: Any, index: int):
info = self.infos[u];
info.index = index;
self.update_infos(u, info);
def stack_contains(self, u: Any) -> bool:
return self.stack.contains(u);
def get_info(self, u: Any) -> NodeInformation:
return self.infos[u];
def get_state(self, u: Any) -> State:
return self.get_info(u).state;
def get_least_index(self, u: Any) -> int:
return self.get_info(u).least_index;
def get_index(self, u: Any) -> int:
return self.get_info(u).index;
def log_info(self, u: Any):
if not self.debug:
return;
info = self.get_info(u);
log_debug(info);

View File

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

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'tsp_algorithm',
];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHOD tsp_algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def tsp_algorithm(
dist: np.ndarray, # NDArray[(Any, Any), float],
optimise = min,
verbose: bool = False,
) -> tuple[float, list[list[int]]]:
m, n = dist.shape[:2];
assert m == n;
memory: Dict[tuple[int, tuple], tuple[float, list[list[int]]]] = dict();
def g(i: int, S: list[int]) -> tuple[float, list[list[int]]]:
# wenn g bereits für den Input definiert ist, gib diesen Wert zurück:
if (i, tuple(S)) not in memory.keys():
if len(S) == 0:
paths = [[i]] if i == 0 else [[i, 0]];
memory[(i, tuple(S))] = (dist[i,0], paths);
else:
values_and_paths = [ (j, *g(j, (*S[:index], *S[(index+1):]))) for index, j in enumerate(S) ];
# berechne d(i,j) + g(j, S \ {i}) for each j in S:
values_and_paths = [ (j, dist[i,j] + value, paths) for j, value, paths in values_and_paths];
value = optimise([value for _, value, _ in values_and_paths]);
paths = [];
for j, value_, paths_ in values_and_paths:
if value_ == value:
paths += [ [i, *path] for path in paths_ ];
memory[(i, tuple(S))] = (value, paths);
return memory[(i, tuple(S))];
# berechne g(0, {1,2,...,n-1}):
optimal_wert = g(0, [i for i in range(1,n)]);
if verbose:
display_computation(n, memory);
return optimal_wert, [];
def display_computation(n: int, memory: Dict[tuple[int, tuple], tuple[float, list[list[int]]]]):
keys = sorted(memory.keys(), key=lambda key: (len(key[1]), key[0], key[1]));
addone = lambda x: x + 1;
for k in range(0,n):
print(f'\x1b[4;1m|S| = {k}:\x1b[0m');
for (i, S) in keys:
if len(S) != k:
continue;
value, paths = memory[(i, S)];
print(f'g({addone(i)}, {list(map(addone, S))}) = {value}');
if len(paths) == 1:
print(f'optimal way: {" -> ".join(map(str, map(addone, paths[0])))}');
else:
print('optimal ways:');
for path in paths:
print(f'* {" -> ".join(map(str, map(addone, path)))}');
print('');
return;