master > master: code-py - tarjan + stacks + logging

This commit is contained in:
RD 2022-04-18 19:04:42 +02:00
parent 2ecf52fe3d
commit 04cdeb4e18
6 changed files with 254 additions and 76 deletions

View File

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
__all__ = [
'log_info',
'log_debug',
'log_warn',
'log_error',
'log_fatal',
];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHODS logging
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log_info(*text: str):
'''
Prints an info message
'''
for line in text:
print("[\x1b[94;1mINFO\x1b[0m] {}".format(line));
return;
def log_debug(*text: str):
'''
Prints a debug message
'''
for line in text:
print("[\x1b[96;1mDEBUG\x1b[95;0m] {}".format(line));
return;
def log_warn(*text: str):
'''
Prints a warning message
'''
for line in text:
print("[\x1b[93;1mWARNING\x1b[0m] {}".format(line));
return;
def log_error(*text: str):
'''
Prints an error message
'''
for line in text:
print("[\x1b[91;1mERROR\x1b[0m] {}".format(line));
return;
def log_fatal(*text: str):
'''
Prints a fatal error message + crashes
'''
for line in text:
print("[\x1b[91;1mFATAL\x1b[0m] {}".format(line));
exit(1);

View File

@ -5,7 +5,9 @@
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
from __future__ import annotations;
from src.local.typing import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
@ -23,22 +25,34 @@ class Graph(object):
'''
a data structure for graphs
'''
nodes: list[str];
edges: list[tuple[str,str]]
nodes: list[Any];
edges: list[tuple[Any,Any]]
def __init__(self, nodes: list[str], edges: list[tuple[str,str]]):
def __init__(self, nodes: list[Any], edges: list[tuple[Any,Any]]):
self.nodes = nodes;
self.edges = edges;
return;
def successor(self, u: str):
def __len__(self) -> int:
return len(self.nodes);
def subgraph(self, nodes: list[Any]) -> Graph:
'''
@returns graph induced by subset of nodes
'''
return Graph(
nodes = [ u for u in self.nodes if u in nodes ],
edges = [ (u, v) for u, v in self.edges if u in nodes and v in nodes ],
);
def successors(self, u: str):
'''
@returns
list of successor nodes
'''
return [ v for (u_, v) in self.edges if u == u_ ];
def predecessor(self, v: str):
def predecessors(self, v: str):
'''
@returns
list of predecessor nodes

View File

@ -5,12 +5,17 @@
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations;
from enum import Enum;
from dataclasses import dataclass;
from dataclasses import field
from platform import node;
from src.local.typing import *;
from src.core.log import *;
from src.stacks.stack import *;
from src.graphs.graph import *
from src.graphs.graph import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS
@ -33,59 +38,134 @@ class State(Enum):
# Tarjan Algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class NodeInformation:
root: int;
index: int;
state: State;
def __init__(self):
self.root = self.index = 0;
self.state = State.UNTOUCHED;
def tarjan_algorithm(G: Graph) -> List[Any]:
def tarjan_algorithm(G: Graph, debug: bool = False) -> List[Any]:
'''
runs the Tarjan-Algorithm to compute the strongly connected components
# 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.
'''
# sonst Stack erstellen und Bearbeitungszustand der Knoten im Speicher notieren:
S = Stack();
index = 0;
infos = { u: NodeInformation() for u in G.nodes };
components = [];
# 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);
def tarjan_visit(v: Any):
'''
recursive depth-first search algorithm to compute components
'''
nonlocal G, S, index, components;
'''
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)));
if not(infos[v].state == State.UNTOUCHED):
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;
index += 1;
infos[v].index = infos[v].root = index;
S.push(v);
infos[v].state = State.PENDING;
# depth first search:
for u in G.successor(v):
tarjan_visit(u);
# remains relevant for v, provided u still in Stack:
if u in S:
infos[v].root = min(infos[v].root, infos[u].root);
infos[v].state = State.FINISHED;
# if at root of component pop everything from stack up to root and add component:
if infos[v].index == infos[v].root:
component = [];
condition_reached_root = False;
while not condition_reached_root:
u = S.pop();
component.append(u);
condition_reached_root = (u == v);
components.append(component);
return;
for v in G.nodes:
tarjan_visit(v);
return components;
info = self.get_info(u);
log_debug(info);

View File

@ -11,6 +11,7 @@ import sys;
os.chdir(os.path.join(os.path.dirname(__file__), '..'));
sys.path.insert(0, os.getcwd());
from src.core.log import *;
from src.graphs.graph import *;
from src.graphs.tarjan import *;
@ -26,14 +27,27 @@ from src.graphs.tarjan import *;
def enter():
## Beispiel aus Seminarblatt 1
## TODO -> schieben in config datei
G = Graph(
nodes=[1,2,3,4,5,6,7],
edges=[(1,2), (1,3), (2,3), (3,4), (4,5), (5,2), (5,6), (5,7), (6,7)],
);
components = tarjan_algorithm(G);
nodes = [1,2,3,4,5,6,7,8];
edges = [
(1,2),
(1,3),
(2,4),
(2,5),
(3,5),
(3,6),
(3,8),
(4,5),
(4,7),
(5,1),
(5,8),
(6,8),
(7,8),
(8,6),
];
G = Graph(nodes=nodes, edges=edges);
components = tarjan_algorithm(G, debug=True);
for component in components:
print(component);
log_debug(component);
return;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -23,10 +23,10 @@ class Stack:
'''
A data structure for stacks
'''
values: list[Any];
elements: list[Any];
def __init__(self):
self.values = [];
self.elements = [];
return;
def __len__(self):
@ -34,16 +34,25 @@ class Stack:
@returns
number of elements in stack
'''
return len(self.values);
return len(self.elements);
def __contains__(self, value: Any) -> bool:
return value in self.values;
return value in self.elements;
def push(self, value: Any):
'''
add element to stack
'''
self.values.append(value);
self.elements.append(value);
def top(self) -> Any:
'''
@returns
top element from stack without removal
'''
if len(self.elements) == 0:
raise Exception('Stack is empty!');
return self.elements[-1];
def pop(self) -> Any:
'''
@ -51,14 +60,11 @@ class Stack:
top element from stack and removes it
'''
value = self.top();
self.values = self.values[:-1];
self.elements = self.elements[:-1];
return value;
def top(self) -> Any:
def contains(self, element: Any) -> bool:
'''
@returns
top element from stack without removal
checks if element in stack:
'''
if len(self.values) == 0:
raise Exception('Stack is empty!');
return self.values[-1];
return element in self.elements;