GraphLib
Bearbeitung der Aufgabe Mini Graph Library für OOP WiSe 2023/24
Loading...
Searching...
No Matches
abstract_graph.h
Go to the documentation of this file.
1#ifndef GRAPHLIB_INTERNAL_ABSTRACT_GRAPH_H
2#define GRAPHLIB_INTERNAL_ABSTRACT_GRAPH_H
3
4#include <cstddef>
5#include <iostream>
6#include <limits>
7#include <ostream>
8#include <queue>
9#include <set>
10#include <stdexcept>
11#include <unordered_map>
12#include <vector>
13
16
35
36namespace GraphLib::Internal {
37
38template <valid_data_type D, valid_id_type I, valid_edge_type<I> E>
39std::ostream& operator<<(std::ostream& os, const Graph<D, I, E>& graph);
40
56template <valid_data_type D, valid_id_type I, valid_edge_type<I> E>
57class Graph {
58public:
59 using data_type = D;
60 using id_type = I;
61 using edge_type = E;
62
63 virtual ~Graph() = default;
64
69 [[nodiscard]] virtual bool is_directed() const = 0;
70
75 [[nodiscard]] virtual bool is_weighted() const = 0;
76
77 //
78 // State Inspection
79 //
80
89 constexpr bool operator==(const Graph& other) const
90 {
91 return this->data_map_ == other.data_map_ && this->adj_list_ == other.adj_list_;
92 }
93
101 bool is_full()
102 {
103 return data_map_.size() == std::numeric_limits<id_type>::max();
104 }
105
112 bool node_exists(const id_type id) const
113 {
114 return adj_list_.has_node_id(id);
115 }
116
122 {
123 return adj_list_.node_id_count();
124 }
125
134 size_t edge_count() const
135 {
136 if (is_directed())
137 return adj_list_.edge_count();
138 else
139 return (adj_list_.edge_count() / 2);
140 }
141
146 std::unordered_set<id_type> get_nodes() const
147 {
148 return adj_list_.get_node_ids();
149 }
150
160 const data_type& get_node_data(const id_type id) const
161 {
162 if (!node_exists(id)) {
163 throw std::invalid_argument("Node does not exist");
164 }
165 return data_map_.at(id);
166 }
167
174 const std::set<id_type> get_neighbors(const id_type id) const
175 {
176 return adj_list_.get_neighboring_node_ids(id);
177 }
178
186 bool has_edge(const id_type from_id, const id_type to_id) const
187 {
188 return adj_list_.has_edge(from_id, to_id);
189 }
190
201 edge_type get_edge(const id_type from_id, const id_type to_id) const
202 {
203 if (!has_edge(from_id, to_id)) {
204 throw std::invalid_argument("Edge does not exist");
205 }
206 return adj_list_.get_edge(from_id, to_id);
207 }
208
215 std::set<edge_type> get_edges(const id_type from_id) const
216 {
217 return adj_list_.get_edges(from_id);
218 }
219
225 void print_data_map() const
226 {
227 std::cout << "data_map_: \n";
228 for (auto i : data_map_) {
229 std::cout << i.first << " : " << i.second << "\n";
230 }
231 std::cout << "---\n";
232 };
233
240 {
241 std::cout << *this << "\n";
242 }
243
252 void print_with_data(std::ostream& os) const
253 {
255 throw std::invalid_argument("The Data is not printable");
256 }
257 adj_list_.print_with_data_map(os, data_map_);
258 }
259
267 {
268 const size_t dim = data_map_.size();
269 std::vector<id_type> nodes;
270 for (auto& pair : data_map_) {
271 nodes.push_back(pair.first);
272 }
273
274#ifndef NDEBUG
275 std::cout << "Dimension: " << dim << "\n";
276 std::cout << "Nodes: ";
277 for (auto& node : nodes) {
278 std::cout << node << " ";
279 }
280 std::cout << "\n";
281#endif
282
283 // Header
284 std::cout << "Adjacency Matrix: \n | ";
285 for (auto& node : nodes) {
286 std::cout << node << " ";
287 }
288 std::cout << "\n";
289 for (size_t idx {}; idx < dim; idx++) {
290 std::cout << "----";
291 }
292 std::cout << "--\n";
293
294 // Content
295 for (auto& base_node : nodes) {
296 std::cout << base_node << " | ";
297 for (auto& node : nodes) {
298 if (adj_list_.has_edge(base_node, node)) {
300 std::cout << adj_list_.get_edge(base_node, node).weight;
301 else
302 std::cout << "X";
303 } else
304 std::cout << ".";
305 std::cout << " ";
306 }
307 std::cout << "\n |\n";
308 }
309 }
310
311 //
312 // State Mutation
313 //
314
327 {
328 id_type id;
329 if (is_full()) {
330 throw std::overflow_error("The Graph is full, no new nodes can be added");
331 }
332 // One of those should always work, don't need to
333 // check for numeric_limits in else clause again.
334 if (!reusable_ids_.empty()) {
335 id = reusable_ids_.front();
336 reusable_ids_.pop();
337 } else {
338 // We lose the ID 0 here, that is intended to avoid a
339 // mismatch between max ID and ID count.
340 auto insertion_id = highest_id_ + 1;
341 while (data_map_.count(insertion_id))
342 ++insertion_id;
343 id = insertion_id;
344 highest_id_ = insertion_id;
345 }
346 const auto [it, inserted] = data_map_.insert({id, data});
347 if (!inserted) {
348 throw std::runtime_error("Insertion failed: key might already exist");
349 }
350 adj_list_.add_node_id(id);
351#ifndef NDEBUG
352 if (is_full()) {
353 std::cout << "Graph is FULL now. We can't add a new node\n";
354 }
355#endif
356 return id;
357 }
358
366 {
368 }
369
381 void add_node_with_id(const id_type id, const data_type data)
382 {
383 if (is_full())
384 throw std::overflow_error("The Graph is full, no new nodes can be added");
385 else if (node_exists(id))
386 throw std::runtime_error("Cannot insert: the given ID already exists");
387
388 const auto [it, inserted] = data_map_.insert({id, data});
389 if (!inserted) {
390 throw std::runtime_error("Insertion failed");
391 }
392 adj_list_.add_node_id(id);
393 }
394
402 {
404 }
405
415 void remove_node(const id_type id)
416 {
417 if (!node_exists(id)) {
418 throw std::invalid_argument("Node with this ID does not exist");
419 }
420 adj_list_.remove_node_id(id);
421 reusable_ids_.push(id);
422 data_map_.erase(id);
423 }
424
432 void set_node_data(const id_type id, const data_type data)
433 {
434 if (!node_exists(id)) {
435 throw std::invalid_argument("Node does not exist");
436 }
437 data_map_.at(id) = data;
438 }
439
450 virtual void add_edge(const id_type from_id, const id_type to_id) = 0;
451
462 virtual void remove_edge(const id_type from_id, const id_type to_id) = 0;
463
464protected:
466 std::unordered_map<id_type, data_type> data_map_ {};
470 std::ostream& os,
472
483 virtual void add_edge_impl(const id_type from_id, const edge_type edge)
484 {
485 if (!node_exists(from_id)) {
486 throw std::invalid_argument("Node with origin ID does not exist");
487 }
488 if (!node_exists(edge.node_id)) {
489 throw std::invalid_argument("Node with target ID to_id does not exist");
490 }
491 if (adj_list_.has_edge(from_id, edge.node_id)) {
492 throw std::invalid_argument("An edge from origin ID to target ID already exists");
493 }
494
495 adj_list_.add_edge(from_id, edge);
496
497 if (!is_directed() && from_id != edge.node_id) {
498 auto reverse_edge = edge;
499 reverse_edge.node_id = from_id;
500 adj_list_.add_edge(edge.node_id, reverse_edge);
501 }
502 }
503
504private:
505 std::queue<id_type> reusable_ids_ {};
506};
507
526template <valid_data_type D, valid_id_type I, valid_edge_type<I> E>
527std::ostream& operator<<(std::ostream& os, const Graph<D, I, E>& graph)
528{
529 os << graph.adj_list_;
530 return os;
531}
532
533} // namespace GraphLib::Internal
534
535#endif
std::ostream & operator<<(std::ostream &os, const Graph< D, I, E > &graph)
Stream insertion operator for any valid graph type.
Generic adjacency list implementation for directed graphs.
Represents a directed (by nature) Adjacency List Graph Structure.
Abstract base class for a generic graph structure.
bool is_full()
Checks if the graph has reached the maximum number of nodes.
const data_type & get_node_data(const id_type id) const
Access the data associated with a node by its ID.
const std::set< id_type > get_neighbors(const id_type id) const
Get neighbors (adjacent node IDs) of a given node.
virtual void add_edge_impl(const id_type from_id, const edge_type edge)
Helper to add an edge including reverse edge if undirected.
std::unordered_map< id_type, data_type > data_map_
Maps node IDs to node data.
virtual bool is_directed() const =0
Check if the graph is directed.
D data_type
Alias for the data type of nodes.
virtual void add_edge(const id_type from_id, const id_type to_id)=0
Add an edge from one node to another.
virtual bool is_weighted() const =0
Check if the graph edges are weighted.
friend std::ostream & operator(std::ostream &os, const Graph< data_type, id_type, edge_type > &graph)
friend for outputting to a std::ostream.
edge_type get_edge(const id_type from_id, const id_type to_id) const
Retrieve the edge information between two nodes.
size_t edge_count() const
Get the number of edges in the graph.
bool node_exists(const id_type id) const
Check if a node with the specified ID exists.
id_type add_node()
Add a new node with default data.
I id_type
Alias for the node ID type.
E edge_type
Alias for the edge type.
bool has_edge(const id_type from_id, const id_type to_id) const
Check if an edge exists between two nodes.
void add_node_with_id(const id_type id, const data_type data)
Insert a new node with a specified ID and associated data.
AdjacencyList< id_type, edge_type > adj_list_
Stores edges as adjacency list.
void print_adjacency_matrix() const
Prints the adjacency matrix of the graph.
void remove_node(const id_type id)
Remove a node by ID.
void print_with_data(std::ostream &os) const
Outputs the graph as an adjacency list to the given stream but with its data.
virtual ~Graph()=default
Virtual destructor for safe polymorphic use.
std::unordered_set< id_type > get_nodes() const
Retrieve the set of all node IDs in the graph.
id_type add_node(const data_type data)
Add a new node with the given data.
id_type node_count() const
Get the number of nodes in the graph.
void add_node_with_id(const id_type id)
Insert a new node with a specified ID.
void set_node_data(const id_type id, const data_type data)
Update the data of a node.
virtual void remove_edge(const id_type from_id, const id_type to_id)=0
Remove an edge from one node to another.
std::set< edge_type > get_edges(const id_type from_id) const
Get all edges originating from a given node.
std::queue< id_type > reusable_ids_
Queue of reusable node IDs freed from removed nodes.
void print_data_map() const
Prints the contents of the data_map_ to standard output.
id_type highest_id_
IDs start at 1 to avoid off-by-one errors when giving node count.
constexpr bool operator==(const Graph &other) const
Equality comparison between graphs.
void print_adjacency_list() const
Outputs the graph as an adjacency list to the given stream.
Checks whether a type supports streaming to std::ostream.
Definition concepts.h:32
Checks whether a type is an aggregate and derived from WeightedEdge<I, W>.
Definition concepts.h:107
Defines C++20 concepts used for type constraints in the GraphLib library.