Compare commits

..

No commits in common. "d474b93c1056c3eb8cf87d57a319b5f280992581" and "d5a79e77cea8b668010a83891a03c5d64deb7e6d" have entirely different histories.

28 changed files with 98 additions and 656 deletions

View File

@ -10,7 +10,6 @@
!/README.md !/README.md
!/LICENSE !/LICENSE
!/requirements !/requirements
!/pyproject.toml
################################################################ ################################################################
# PROJECT FILES # PROJECT FILES

View File

@ -21,12 +21,12 @@ endif
# Macros # Macros
################################ ################################
define create_file_if_not_exists define create_folder_if_not_exists
@touch "$(1)"; @if ! [ -d "$(1)" ]; then mkdir "$(1)"; fi
endef endef
define create_folder_if_not_exists define create_folder_if_not_exists
if ! [ -d "$(1)" ]; then mkdir "$(1)"; fi @touch "$(1)";
endef endef
define delete_if_file_exists define delete_if_file_exists
@ -63,15 +63,13 @@ all: setup run
################################ ################################
# TARGETS: testing # TARGETS: testing
################################ ################################
tests: unit-tests unit-test: unit-tests
unit-tests: unit-tests:
@# For logging purposes (since stdout is rechanneled): @cd tests && \
@$(call delete_if_file_exists,logs/debug.log) ${PYTHON} -m unittest discover -v \
@$(call create_folder_if_not_exists,logs) --start-directory "." \
@$(call create_file_if_not_exists,logs/debug.log) --top-level-directory ".." \
@# for python unit tests: --pattern "test_*.py"
@${PYTHON} -m pytest tests --cache-clear --verbose -k test_
@cat logs/debug.log
################################ ################################
# AUXILIARY (INTERNAL TARGETS) # AUXILIARY (INTERNAL TARGETS)
################################ ################################

View File

@ -1,8 +0,0 @@
[tool.pytest.ini_options]
minversion = "7.1.1"
testpaths = [
"tests",
]
python_files = [
"**/test_*.py",
]

View File

@ -1,6 +1,4 @@
pip>=22.0.4 pip>=22.0.4
pytest>=7.1.1
pytest-lazy-fixture>=0.6.3
typing>=3.7.4.3 typing>=3.7.4.3
numpy>=1.22.3 numpy>=1.22.3
pandas>=1.4.1 pandas>=1.4.1

View File

@ -1,64 +0,0 @@
#!/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,9 +5,7 @@
# IMPORTS # IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations; #
from src.local.typing import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS # EXPORTS
@ -25,34 +23,22 @@ class Graph(object):
''' '''
a data structure for graphs a data structure for graphs
''' '''
nodes: list[Any]; nodes: list[str];
edges: list[tuple[Any,Any]] edges: list[tuple[str,str]]
def __init__(self, nodes: list[Any], edges: list[tuple[Any,Any]]): def __init__(self, nodes: list[str], edges: list[tuple[str,str]]):
self.nodes = nodes; self.nodes = nodes;
self.edges = edges; self.edges = edges;
return; return;
def __len__(self) -> int: def successor(self, u: str):
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 @returns
list of successor nodes list of successor nodes
''' '''
return [ v for (u_, v) in self.edges if u == u_ ]; return [ v for (u_, v) in self.edges if u == u_ ];
def predecessors(self, v: str): def predecessor(self, v: str):
''' '''
@returns @returns
list of predecessor nodes list of predecessor nodes

View File

@ -5,17 +5,12 @@
# IMPORTS # IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations;
from enum import Enum; from enum import Enum;
from dataclasses import dataclass;
from dataclasses import field
from platform import node;
from src.local.typing import *; from src.local.typing import *;
from src.core.log import *;
from src.stacks.stack import *; from src.stacks.stack import *;
from src.graphs.graph import *; from src.graphs.graph import *
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS # EXPORTS
@ -38,134 +33,59 @@ class State(Enum):
# Tarjan Algorithm # Tarjan Algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def tarjan_algorithm(G: Graph, debug: bool = False) -> List[Any]: class NodeInformation:
''' root: int;
# Tarjan Algorithm # index: int;
Runs the Tarjan-Algorithm to compute the strongly connected components. state: State;
'''
# 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 __init__(self):
self.root = self.index = 0;
self.state = State.UNTOUCHED;
def tarjan_visit(G: Graph, u: Any, ctx: Context): def tarjan_algorithm(G: Graph) -> List[Any]:
''' '''
Recursive depth-first search algorithm to compute strongly components of a graph. runs the Tarjan-Algorithm to compute the strongly connected components
''' '''
# Place node on stack + initialise visit-index + component-index. # sonst Stack erstellen und Bearbeitungszustand der Knoten im Speicher notieren:
ctx.max_index += 1; S = Stack();
ctx.push(u); index = 0;
ctx.set_least_index(u, ctx.max_index); infos = { u: NodeInformation() for u in G.nodes };
ctx.set_index(u, ctx.max_index); components = [];
ctx.set_state(u, State.PENDING);
def tarjan_visit(v: Any):
''' '''
Compute strongly connected components of each child node. recursive depth-first search algorithm to compute components
NOTE: Child nodes remain on stack, if and only if parent is in same component.
''' '''
for v in G.successors(u): nonlocal G, S, index, components;
# 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); if not(infos[v].state == State.UNTOUCHED):
ctx.log_info(u); return;
# If at least-index of component pop everything from stack up to least index and add component: index += 1;
if ctx.get_index(u) == ctx.get_least_index(u): 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 = []; component = [];
while True: condition_reached_root = False;
v = ctx.top(); while not condition_reached_root:
ctx.pop(); u = S.pop();
component.append(v); component.append(u);
if u == v: condition_reached_root = (u == v);
break; components.append(component);
ctx.components.append(component);
return; return;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for v in G.nodes:
# AUXILIARY context variables for algorithm tarjan_visit(v);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return components;
@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

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

View File

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

View File

@ -1,26 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from unittest import TestCase;
from pytest import mark;
from pytest import fixture;
from tests.core.log import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@fixture(scope='module')
def test():
return TestCase();

View File

@ -1,17 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHODS - logging
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def log(*lines: str):
with open('logs/debug.log', 'a') as fp:
for line in lines:
print(line, end='\n', file=fp);

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from pytest import mark;
from pytest import fixture;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from pytest import mark;
from pytest import fixture;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test logging
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from unittest import TestCase;
from pytest import mark;
from pytest import fixture;
from tests.core.log import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# CONSTANTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from contextlib import nullcontext as does_not_raise
from collections import Counter;
from pytest import mark;
from pytest import fixture;
from unittest import TestCase;
from src.graphs.graph import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@fixture(scope='module')
def graph1() -> Graph:
return Graph(
nodes=[1,2,3,4,5,6,7,8],
edges=[(1,2), (1, 3), (2,3), (3,4), (4, 5), (5, 6), (6, 2), (6, 7), (6, 8)],
);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test Graph
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@mark.usefixtures('test')
def test_graph_creation(test: TestCase):
with does_not_raise():
G = Graph(
[5, 7, 8],
[(5,7), (7, 8)]
);
test.assertEqual(len(G), 3);
G = Graph(
["5", "7", "8", "10"],
[("5", "7"), ("7", "8")]
);
test.assertEqual(len(G), 4);
G = Graph(nodes=[], edges=[]);
test.assertEqual(len(G), 0);
@mark.usefixtures('test', 'graph1')
def test_graph_subgraph(test: TestCase, graph1: Graph):
sub_gph = graph1.subgraph([2,4,5,6,8]);
test.assertCountEqual(sub_gph.edges, [(6,2), (4,5), (5,6), (6,8)]);
@mark.usefixtures('test', 'graph1')
def test_graph_successors_and_predecessors(test: TestCase, graph1: Graph):
test.assertCountEqual(graph1.successors(1), [2, 3]);
test.assertEqual(len(graph1.successors(8)), 0);
test.assertCountEqual(graph1.successors(6), [2, 7, 8]);
test.assertEqual(len(graph1.predecessors(1)), 0);
test.assertCountEqual(graph1.predecessors(8), [6]);
test.assertCountEqual(graph1.predecessors(6), [5]);

View File

@ -1,119 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from contextlib import nullcontext as does_not_raise
from collections import Counter;
from pytest import mark;
from pytest import fixture;
from pytest import lazy_fixture;
from unittest import TestCase;
from src.local.typing import *;
from src.graphs.graph import *;
from src.graphs.tarjan import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TESTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# @mark.usefixtures('my_data', 'first_entry', 'second_entry')
# def test_format_data_for_display(my_data):
# assert format_data_for_display(my_data) == [
# "Alfonsa Ruiz: Senior Software Engineer",
# "Sayid Khan: Project Manager",
# ];
# @mark.usefixtures('my_data', 'first_entry', 'second_entry')
# def test_something(my_data):
# assert 1+1 == 2;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Fixtures
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@fixture(scope='module')
def graph1() -> Graph:
return Graph(
nodes=[1,2,3,4],
edges=[(1,2), (2,4), (4,2)],
);
@fixture(scope='module')
def graph2() -> Graph:
return 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)],
);
@fixture(scope='module')
def graph3() -> Graph:
return Graph(
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),
],
);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test Tarjan-Algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@mark.parametrize(
('G', 'expected'),
[
(lazy_fixture('graph1'), [[1], [3], [2,4]]),
(lazy_fixture('graph2'), [[1], [6], [7], [2,3,4,5]]),
(lazy_fixture('graph3'), [[1,2,3,4,5], [7], [6,8]]),
],
)
@mark.usefixtures('test')
def test_tarjan(test, G, expected):
components = tarjan_algorithm(G, False);
assert_components_eq(test, components, expected);
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY METHODS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def assert_components_eq(test: TestCase, components1: list[list[Any]], components2: list[list[Any]]):
result = check_components_eq(test, components1, components2);
test.assertTrue(result);
def check_components_eq(test: TestCase, components1: list[list[Any]], components2: list[list[Any]]) -> bool:
if len(components1) != len(components2):
return False;
for component1 in components1:
found = False;
for component2 in components2:
try:
test.assertCountEqual(component1, component2);
found = True;
break;
except:
continue;
if not found:
return False;
return True;

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from pytest import mark;
from pytest import fixture;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# FIXTURES
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#

View File

@ -1,63 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from contextlib import nullcontext as does_not_raise
import pytest;
from pytest import mark;
from unittest import TestCase;
from src.stacks.stack import Stack;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Fixtures
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def format_data_for_display(data):
return [ '{given_name} {family_name}: {title}'.format(**row) for row in data ];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Test stack
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@mark.usefixtures('test')
def test_stack_initialisation(test: TestCase):
stack = Stack();
test.assertEqual(len(stack), 0);
@mark.usefixtures('test')
def test_stack_push(test: TestCase):
stack = Stack();
test.assertEqual(len(stack), 0);
stack.push('hallo');
test.assertEqual(len(stack), 1);
stack.push('welt');
test.assertEqual(len(stack), 2);
stack.push('!');
test.assertEqual(len(stack), 3);
@mark.usefixtures('test')
def test_stack_no_error(test: TestCase):
stack = Stack();
stack.push('hallo');
stack.push('welt');
stack.push('!');
with does_not_raise():
stack.pop();
stack.pop();
stack.pop();
@mark.usefixtures('test')
def test_stack_error_po(test: TestCase):
stack = Stack();
stack.push('hallo');
stack.push('welt');
stack.push('!');
with pytest.raises(Exception):
stack.pop();
stack.pop();
stack.pop();
stack.pop();

View File

@ -19,12 +19,12 @@ endif
# Macros # Macros
################################ ################################
define create_file_if_not_exists define create_folder_if_not_exists
@touch "$(1)"; @if ! [ -d "$(1)" ]; then mkdir "$(1)"; fi
endef endef
define create_folder_if_not_exists define create_folder_if_not_exists
@if ! [ -d "$(1)" ]; then mkdir "$(1)"; fi @touch "$(1)";
endef endef
define delete_if_file_exists define delete_if_file_exists

View File

@ -48,7 +48,7 @@ fn tarjan_visit<T>(gph: &Graph<T>, &u: &T, ctx: &mut Context<T>)
{ {
// Place node on stack + initialise visit-index + component-index. // Place node on stack + initialise visit-index + component-index.
ctx.max_index += 1; ctx.max_index += 1;
ctx.stack_push(&u); ctx.push(&u);
ctx.set_root(&u, ctx.max_index); ctx.set_root(&u, ctx.max_index);
ctx.set_index(&u, ctx.max_index); ctx.set_index(&u, ctx.max_index);
ctx.set_state(&u, State::PENDING); ctx.set_state(&u, State::PENDING);
@ -63,19 +63,18 @@ fn tarjan_visit<T>(gph: &Graph<T>, &u: &T, ctx: &mut Context<T>)
tarjan_visit(gph, &v, ctx); tarjan_visit(gph, &v, ctx);
} }
// Update associated component-index of parent node, if in same component as child: // Update associated component-index of parent node, if in same component as child:
if ctx.stack_contains(&v) { if ctx.stack.contains(&v) {
ctx.set_root(&u, value_min!(ctx.get_root(&u), ctx.get_root(&v))); ctx.set_root(&u, value_min!(ctx.get_root(&u), ctx.get_root(&v)));
} }
} }
ctx.set_state(&u, State::FINISHED); ctx.set_state(&u, State::FINISHED);
ctx.log_info(&u); ctx.log_info(&u);
// If at root of component pop everything from stack up to root and add component:
if ctx.get_index(&u) == ctx.get_root(&u) { if ctx.get_index(&u) == ctx.get_root(&u) {
let mut component: Vec<T> = Vec::new(); let mut component: Vec<T> = Vec::new();
loop { loop {
let v = ctx.stack_top(); let v = ctx.top();
ctx.stack_pop(); ctx.pop();
component.push(v.clone()); component.push(v.clone());
if u == v { break; } if u == v { break; }
} }
@ -120,15 +119,15 @@ impl<T> Context<T>
}; };
} }
fn stack_push(self: &mut Self, u: &T) { fn push(self: &mut Self, u: &T) {
self.stack.push(u.clone()); self.stack.push(u.clone());
} }
fn stack_top(self: &mut Self) -> T { fn top(self: &mut Self) -> T {
return self.stack.top(); return self.stack.top();
} }
fn stack_pop(self: &mut Self) -> T { fn pop(self: &mut Self) -> T {
return self.stack.pop(); return self.stack.pop();
} }
@ -154,10 +153,6 @@ impl<T> Context<T>
self.update_infos(u, info); self.update_infos(u, info);
} }
fn stack_contains(self: &Self, u: &T) -> bool {
return self.stack.contains(u);
}
fn get_info(self: &Self, u: &T) -> NodeInformation<T> { fn get_info(self: &Self, u: &T) -> NodeInformation<T> {
return *self.infos.get(u).unwrap(); return *self.infos.get(u).unwrap();
} }

View File

@ -55,7 +55,7 @@ fn fixture_graph3() -> graph::Graph<i32> {
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Test Tarjan-Algorithm // Test Graph
// ---------------------------------------------------------------- // ----------------------------------------------------------------
#[rstest] #[rstest]

View File

@ -7,6 +7,10 @@ extern crate closure;
use ads2::stacks::stack; use ads2::stacks::stack;
// ----------------------------------------------------------------
// Test regex
// ----------------------------------------------------------------
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Test stack // Test stack
// ---------------------------------------------------------------- // ----------------------------------------------------------------