master > master: code-py - tarjan + stacks + logging
This commit is contained in:
parent
2ecf52fe3d
commit
04cdeb4e18
0
code/python/src/core/__init__.py
Normal file
0
code/python/src/core/__init__.py
Normal file
64
code/python/src/core/log.py
Normal file
64
code/python/src/core/log.py
Normal 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);
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user