master > master: code py - refactoring
This commit is contained in:
0
code/python/src/algorithms/__init__.py
Normal file
0
code/python/src/algorithms/__init__.py
Normal file
18
code/python/src/algorithms/hirschberg/__init__.py
Normal file
18
code/python/src/algorithms/hirschberg/__init__.py
Normal 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',
|
||||
];
|
||||
153
code/python/src/algorithms/hirschberg/algorithms.py
Normal file
153
code/python/src/algorithms/hirschberg/algorithms.py
Normal 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);
|
||||
123
code/python/src/algorithms/hirschberg/display.py
Normal file
123
code/python/src/algorithms/hirschberg/display.py
Normal 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;
|
||||
127
code/python/src/algorithms/hirschberg/matrix.py
Normal file
127
code/python/src/algorithms/hirschberg/matrix.py
Normal 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;
|
||||
125
code/python/src/algorithms/hirschberg/paths.py
Normal file
125
code/python/src/algorithms/hirschberg/paths.py
Normal 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;
|
||||
16
code/python/src/algorithms/tarjan/__init__.py
Normal file
16
code/python/src/algorithms/tarjan/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# IMPORTS
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
from src.algorithms.tarjan.algorithms import *;
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# EXPORTS
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
__all__ = [
|
||||
'tarjan_algorithm',
|
||||
];
|
||||
167
code/python/src/algorithms/tarjan/algorithms.py
Normal file
167
code/python/src/algorithms/tarjan/algorithms.py
Normal 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);
|
||||
16
code/python/src/algorithms/tsp/__init__.py
Normal file
16
code/python/src/algorithms/tsp/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# IMPORTS
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
from src.algorithms.tsp.algorithms import *;
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# EXPORTS
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
__all__ = [
|
||||
'tsp_algorithm',
|
||||
];
|
||||
75
code/python/src/algorithms/tsp/algorithms.py
Normal file
75
code/python/src/algorithms/tsp/algorithms.py
Normal 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;
|
||||
Reference in New Issue
Block a user