Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Graph class #172

Merged
merged 44 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d5afb30
Add `Graph` initial implementation
Xrayez Jan 27, 2022
e4b6ba8
Add ability to remove vertices, added more methods to `Graph`
Xrayez Jan 28, 2022
fc22dfe
Rename `bidirectional` to `directed` in `GraphEdge`
Xrayez Jan 28, 2022
b42552a
Add method to clear edges in `Graph`
Xrayez Jan 28, 2022
7159742
Simplify vertex removal in `Graph`
Xrayez Jan 29, 2022
f20d449
Add a method to remove individual edges from `Graph`
Xrayez Jan 29, 2022
4aa75ca
Fix heap use after free in `Graph`
Xrayez Jan 29, 2022
5f1cf6c
Add missing `get_vertex_list()` to `Graph`
Xrayez Jan 30, 2022
dea613f
Use `HashMap` for `Graph` vertices
Xrayez Jan 30, 2022
fd41b73
Major `Graph` refactor using `HashMap`
Xrayez Jan 31, 2022
1489b1a
Fix tests warning treated as error in `Graph`
Xrayez Jan 31, 2022
ca8a270
Rename `vertex_a/b` to `a/b` in `GraphEdge`
Xrayez Jan 31, 2022
201cb7b
Refactor retrieval of adjacency list in `Graph`
Xrayez Jan 31, 2022
9c034d5
Move neighbor methods to `GraphVertex`
Xrayez Jan 31, 2022
9f54b56
Add methods to count neighbors to `Graph`
Xrayez Jan 31, 2022
fb294bc
Add a way to override creation of vertices and edges in `Graph`
Xrayez Jan 31, 2022
e7761d8
Add `find_vertex()` to `Graph`
Xrayez Feb 1, 2022
8ebdfc9
Add `inner_test_case` option
Xrayez Feb 3, 2022
a7fde1b
Replace list with vector for representing multiple edges in `Graph`
Xrayez Feb 3, 2022
b493398
Use typedef for edge list in `Graph`
Xrayez Feb 3, 2022
af6fa42
Add connectivity methods to `Graph`
Xrayez Feb 4, 2022
ab371d2
Use `Set` for DFS graph traversal
Xrayez Feb 5, 2022
af3e42e
Fix stack naming conventions
Xrayez Feb 5, 2022
decc1ad
Simplify DFS traversal in `Graph`
Xrayez Feb 5, 2022
a7b44a5
Add a way to customize graph traversal via `GraphIterator`
Xrayez Feb 6, 2022
26d767f
Add a way to return all connected components in the `Graph`
Xrayez Feb 8, 2022
ddb7173
Fully document `Graph` related classes
Xrayez Feb 8, 2022
c05521d
Implement built-in breadth-first search (BFS) iterator for `Graph`
Xrayez Feb 9, 2022
b93f309
Make `Graph` serializable
Xrayez Feb 9, 2022
0c86c41
Implement Kruskal's algorithm for finding MST in `Graph`
Xrayez Feb 10, 2022
bb867c3
Validate input parameters in `Graph`
Xrayez Feb 11, 2022
87e1af9
Improve edge key hashing and comparison in `Graph`
Xrayez Feb 11, 2022
9ee8d39
Speedup cleanup of `Graph`
Xrayez Feb 12, 2022
e0ba42d
Suggest more simple method to inverse weight for MST in `Graph`
Xrayez Feb 12, 2022
9045aae
Implement Dijkstra's algorithm for finding shortest paths in `Graph`
Xrayez Feb 14, 2022
1b18cf4
Add a way to retrieve master `Graph` associated with a `GraphVertex`
Xrayez Feb 15, 2022
3219cf5
Allow to add edges by vertex values in `Graph`
Xrayez Feb 15, 2022
86d5a24
Expose `get_graph()` in `GraphEdge`
Xrayez Feb 15, 2022
a4c10f9
Test vertex and edge destructor in `Graph`
Xrayez Feb 15, 2022
fb78ef1
Validate ownership of vertices while adding edges in `Graph`
Xrayez Feb 15, 2022
21fb650
Always exit tests while running in CI environment
Xrayez Feb 16, 2022
f72b672
Implement GDScript iterators for `Graph` and `GraphVertex`
Xrayez Feb 16, 2022
0a51819
Rename `get_vertex/edge/list()` to `get_vertices/edges()` in `Graph`
Xrayez Feb 16, 2022
f39d38d
Fix crash with GDScript `Graph` next iterator
Xrayez Feb 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ void register_core_types() {
ClassDB::register_class<ListNode>();
ClassDB::register_class<LinkedList>();

ClassDB::register_class<Graph>();
ClassDB::register_class<GraphVertex>();
ClassDB::register_class<GraphEdge>();

ClassDB::register_class<VariantMap>();
ClassDB::register_class<VariantResource>();

Expand Down
370 changes: 370 additions & 0 deletions core/types/graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,370 @@
#include "graph.h"

#include "core/set.h"

#ifdef DEBUG_ENABLED

#define ERR_INVALID_VERTEX(m_v) \
ERR_FAIL_NULL(m_v); \
ERR_FAIL_COND(!graph->data.has(m_v));

#define ERR_INVALID_VERTEX_V(m_v, m_ret) \
ERR_FAIL_NULL_V(m_v, m_ret); \
ERR_FAIL_COND_V(!graph->data.has(m_v), m_ret);

#define ERR_INVALID_VERTICES(m_a, m_b) \
ERR_FAIL_NULL(m_a); \
ERR_FAIL_NULL(m_b); \
ERR_FAIL_COND(!graph->data.has(m_a)); \
ERR_FAIL_COND(!graph->data.has(m_b));

#define ERR_INVALID_VERTICES_V(m_a, m_b, m_ret) \
ERR_FAIL_NULL_V(m_a, m_ret); \
ERR_FAIL_NULL_V(m_b, m_ret); \
ERR_FAIL_COND_V(!graph->data.has(m_a), m_ret); \
ERR_FAIL_COND_V(!graph->data.has(m_b), m_ret);
#else

#define ERR_INVALID_VERTEX(m_v)
#define ERR_INVALID_VERTEX_V(m_v, m_ret)
#define ERR_INVALID_VERTICES(m_a, m_b)
#define ERR_INVALID_VERTICES_V(m_a, m_b, m_ret)

#endif // DEBUG_ENABLED

GraphVertex *Graph::add_vertex(const Variant &p_value) {
GraphVertex *v = memnew(GraphVertex);

v->value = p_value;
graph->data[v] = List<GraphEdge *>();
v->graph = graph;

return v;
}

void Graph::remove_vertex(GraphVertex *v) {
ERR_INVALID_VERTEX(v);
// Calls into GraphData::remove_vertex() during NOTIFICATION_PREDELETE
memdelete(v);
}

void GraphData::remove_vertex(GraphVertex *v) {
// Find all vertices connected to this one, including the vertex to be removed.
Vector<GraphVertex *> closed_neighborhood;
closed_neighborhood.push_back(v);

List<GraphEdge *> &list_v = data[v];
for (List<GraphEdge *>::Element *E = list_v.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (v == edge->a) {
closed_neighborhood.push_back(edge->b);
} else {
closed_neighborhood.push_back(edge->a);
}
}
// Find edges that are associated with the vertices above.
Set<GraphEdge *> edges_to_delete;

for (int i = 0; i < closed_neighborhood.size(); ++i) {
GraphVertex *n = closed_neighborhood[i];
List<GraphEdge *> &list_n = data[n];

for (List<GraphEdge *>::Element *E = list_n.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (v == edge->a || v == edge->b) {
edges_to_delete.insert(edge);
list_n.erase(E);
}
}
}
// Delete all edges associated with the vertex.
for (Set<GraphEdge *>::Element *E = edges_to_delete.front(); E; E = E->next()) {
memdelete(E->get());
}
// Remove the vertex itself from the graph.
v->graph = nullptr;
bool erased = data.erase(v);
ERR_FAIL_COND(!erased);
}

void GraphData::remove_edge(GraphEdge *e) {
e->graph = nullptr;
}

Array Graph::get_neighbors(GraphVertex *v) {
ERR_INVALID_VERTEX_V(v, Array());

Array neighborhood;
const List<GraphEdge *> &list = graph->data[v];

for (const List<GraphEdge *>::Element *E = list.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (edge->directed) {
continue;
}
if (v == edge->a) {
neighborhood.push_back(edge->b);
} else {
neighborhood.push_back(edge->a);
}
}
return neighborhood;
}

Array Graph::get_successors(GraphVertex *v) {
ERR_INVALID_VERTEX_V(v, Array());

Array successors;
const List<GraphEdge *> &list = graph->data[v];

for (const List<GraphEdge *>::Element *E = list.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (!edge->directed) {
continue;
}
if (v == edge->a) {
successors.push_back(edge->b);
}
}
return successors;
}

Array Graph::get_predecessors(GraphVertex *v) {
ERR_INVALID_VERTEX_V(v, Array());

Array predecessors;
const List<GraphEdge *> &list = graph->data[v];

for (const List<GraphEdge *>::Element *E = list.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (!edge->directed) {
continue;
}
if (v == edge->b) {
predecessors.push_back(edge->a);
}
}
return predecessors;
}

GraphEdge *Graph::_add_edge(const Variant &p_a, const Variant &p_b, real_t p_weight, bool p_directed) {
ERR_FAIL_COND_V(p_a.get_type() == Variant::NIL, nullptr);
ERR_FAIL_COND_V(p_b.get_type() == Variant::NIL, nullptr);

GraphVertex *a;
GraphVertex *b;

if (p_a.get_type() == Variant::OBJECT) {
a = Object::cast_to<GraphVertex>(p_a);
ERR_FAIL_NULL_V(a, nullptr);
} else {
a = add_vertex(p_a);
}
if (p_b.get_type() == Variant::OBJECT) {
b = Object::cast_to<GraphVertex>(p_b);
ERR_FAIL_NULL_V(b, nullptr);
} else {
b = add_vertex(p_b);
}
GraphEdge *edge = memnew(GraphEdge);
edge->a = a;
edge->b = b;
edge->weight = p_weight;
edge->directed = p_directed;

graph->data[a].push_back(edge);
graph->data[b].push_back(edge);
edge->graph = graph;

return edge;
}

GraphEdge *Graph::add_edge(const Variant &p_a, const Variant &p_b, real_t p_weight) {
return _add_edge(p_a, p_b, p_weight, false);
}

GraphEdge *Graph::add_directed_edge(const Variant &p_a, const Variant &p_b, real_t p_weight) {
return _add_edge(p_a, p_b, p_weight, true);
}

void Graph::remove_edge(GraphEdge *e) {
ERR_FAIL_NULL(e);
ERR_INVALID_VERTEX(e->a);
ERR_INVALID_VERTEX(e->b);

GraphVertex *v[2] = {e->a, e->b};

for (int i = 0; i < 2; ++i) {
List<GraphEdge *> &list = graph->data[v[i]];

for (List<GraphEdge *>::Element *E = list.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (e == edge) {
list.erase(E);
}
}
}
memdelete(e);
}

GraphEdge *Graph::find_edge(GraphVertex *a, GraphVertex *b) const {
ERR_INVALID_VERTICES_V(a, b, nullptr);

const List<GraphEdge *> &list = graph->data[a];
for (const List<GraphEdge *>::Element *E = list.front(); E; E = E->next()) {
GraphEdge *edge = E->get();
if (a == edge->a && b == edge->b) {
return edge;
}
if (!edge->directed && a == edge->b && b == edge->a) {
return edge;
}
}
return nullptr;
}

bool Graph::has_edge(GraphVertex *a, GraphVertex *b) const {
return find_edge(a, b) != nullptr;
}

Array Graph::get_edge_list(GraphVertex *a, GraphVertex *b) const {
Array edge_list;

Set<GraphEdge *> edge_set;

if (!a && !b) {
// Get all edges in the graph.
for (Map<GraphVertex *, List<GraphEdge *>>::Element *E = graph->data.front(); E; E = E->next()) {
const List<GraphEdge *> &list = E->get();
for (const List<GraphEdge *>::Element *I = list.front(); I; I = I->next()) {
edge_set.insert(I->get());
}
}
} else {
// Get all edges between vertices.
for (Map<GraphVertex *, List<GraphEdge *>>::Element *E = graph->data.front(); E; E = E->next()) {
const List<GraphEdge *> &list = E->get();
for (const List<GraphEdge *>::Element *I = list.front(); I; I = I->next()) {
GraphEdge *edge = I->get();
if (a == edge->a && b == edge->b) {
edge_set.insert(edge);
} else if (!edge->directed && a == edge->b && b == edge->a) {
edge_set.insert(edge);
}
}
}
}
for (const Set<GraphEdge *>::Element *I = edge_set.front(); I; I = I->next()) {
edge_list.push_back(I->get());
}
return edge_list;
}

int Graph::get_edge_count() const {
Set<GraphEdge *> edge_set;

for (const Map<GraphVertex *, List<GraphEdge *>>::Element *E = graph->data.front(); E; E = E->next()) {
const List<GraphEdge *> &list = E->get();
for (const List<GraphEdge *>::Element *I = list.front(); I; I = I->next()) {
edge_set.insert(I->get());
}
}
return edge_set.size();
}

void Graph::clear() {
Vector<GraphVertex *> to_remove;
for (Map<GraphVertex *, List<GraphEdge *>>::Element *E = graph->data.front(); E; E = E->next()) {
to_remove.push_back(E->key());
}
for (int i = 0; i < to_remove.size(); ++i) {
memdelete(to_remove[i]); // This will also delete associated edges.
}
}

void Graph::clear_edges() {
Set<GraphEdge *> edge_set;

for (Map<GraphVertex *, List<GraphEdge *>>::Element *E = graph->data.front(); E; E = E->next()) {
List<GraphEdge *> &list = E->get();
for (List<GraphEdge *>::Element *I = list.front(); I; I = I->next()) {
edge_set.insert(I->get());
list.erase(I);
}
}
for (const Set<GraphEdge *>::Element *I = edge_set.front(); I; I = I->next()) {
memdelete(I->get());
}
}

void Graph::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_vertex", "value"), &Graph::add_vertex);
ClassDB::bind_method(D_METHOD("remove_vertex", "vertex"), &Graph::remove_vertex);
ClassDB::bind_method(D_METHOD("has_vertex", "vertex"), &Graph::has_vertex);
ClassDB::bind_method(D_METHOD("get_vertex_count"), &Graph::get_vertex_count);

ClassDB::bind_method(D_METHOD("get_neighbors", "vertex"), &Graph::get_neighbors);
ClassDB::bind_method(D_METHOD("get_successors", "vertex"), &Graph::get_successors);
ClassDB::bind_method(D_METHOD("get_predecessors", "vertex"), &Graph::get_predecessors);

ClassDB::bind_method(D_METHOD("add_edge", "a", "b", "weight"), &Graph::add_edge, DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("add_directed_edge", "from", "to", "weight"), &Graph::add_directed_edge, DEFVAL(1.0));
ClassDB::bind_method(D_METHOD("remove_edge", "edge"), &Graph::remove_edge);
ClassDB::bind_method(D_METHOD("find_edge", "a", "b"), &Graph::find_edge);
ClassDB::bind_method(D_METHOD("has_edge", "a", "b"), &Graph::has_edge);
ClassDB::bind_method(D_METHOD("get_edge_list", "a", "b"), &Graph::get_edge_list, DEFVAL(Variant()), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("get_edge_count"), &Graph::get_edge_count);

ClassDB::bind_method(D_METHOD("clear"), &Graph::clear);
ClassDB::bind_method(D_METHOD("clear_edges"), &Graph::clear_edges);
}

Graph::Graph() {
graph = memnew(GraphData);
}

Graph::~Graph() {
clear();
if (graph) {
memdelete(graph);
}
}

void GraphVertex::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PREDELETE: {
if (graph) {
graph->remove_vertex(this);
}
} break;
}
}

void GraphVertex::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &GraphVertex::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &GraphVertex::get_value);

ADD_PROPERTY(PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), "set_value", "get_value");
}

void GraphEdge::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PREDELETE: {
if (graph) {
graph->remove_edge(this);
}
} break;
}
}

void GraphEdge::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_vertex_a"), &GraphEdge::get_vertex_a);
ClassDB::bind_method(D_METHOD("get_vertex_b"), &GraphEdge::get_vertex_b);

ClassDB::bind_method(D_METHOD("set_weight", "weight"), &GraphEdge::set_weight);
ClassDB::bind_method(D_METHOD("get_weight"), &GraphEdge::get_weight);

ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vertex_a"), "", "get_vertex_a");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "vertex_b"), "", "get_vertex_b");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "weight"), "set_weight", "get_weight");
}
Loading