I recently had to submit my 12th grade final project in C++, so I decided to make a game engine! That panned out just as you would expect (I am not touching OpenGL until they touch OOP), so I switched and ended up making a project on graph coloring instead!
For context, in the actual project this was used to calculate an exam schedule for a list of students read from a file, but I will not be including that since it would make things too long.
Did I follow good coding practices and make my teacher proud? Note that using STL was not the point of the project.
BTW, can anyone calculate the time complexity of this graph-coloring algorithm (in terms of the \$O(\ldots{})\$ notation)? I have no idea 😅.
Here is some further context:
- There is a literal limit on how advanced this code can be. My board doesn't specify a version, so all teachers default to (shock, and horror! ⊂(゜Д゜⊂) ) Turbo C++. It is a miracle I could sneak in templates.
- There are actually 40 other scripts where I had to make a linked-list twice, so you know where I copied it from. The graph code is virgin fresh tho.
- If you feel like it, compile it! Here is some test code(compiled with G++ 7.3.0) for a graph of integers. The repo also has the final project and the other scripts, so please do check them out too, if you are up to it!
graph.h
#if !defined(VARAD_GRAPH_H_INCLUDED)
#define VARAD_GRAPH_H_INCLUDED
#include "list.h"
namespace graph_h {
// Add const to type if it is a pointer
template <class T>
struct AddConstToType {
typedef T type;
};
template <class T>
struct AddConstToType<T*> {
typedef const T* type;
};
template <class T>
class BinaryFunctor {
public :
virtual bool function(const typename AddConstToType<T>::type& a, const typename AddConstToType<T>::type& b) const = 0;
inline bool operator()(const typename AddConstToType<T>::type& a, const typename AddConstToType<T>::type& b) const {
return function(a, b);
}
};
}
template <class T>
class Graph {
public :
class Vertex {
public :
T data;
List<Vertex*> edges;
// Vertex Constructors
Vertex() : data(), edges() {}
// Weird cast of const T new_data to T so that it can be copied to data
Vertex(const typename graph_h::AddConstToType<T>::type& new_data) : data((T)new_data), edges() {}
Vertex(const typename graph_h::AddConstToType<T>::type& new_data, Vertex** edge_list, unsigned int edge_count)
: data((T)new_data), edges(edge_list, edge_count) {}
Vertex(const Vertex& old_vertex) : data(old_vertex.data), edges(old_vertex.edges) {}
// Equivalence checks are dependent on the Vertex data, not the edges
inline bool operator==(const Vertex& other_vertex) const {
return data == other_vertex.data;
}
inline bool operator!=(const Vertex& other_vertex) const {
return not (*this == other_vertex.data) ;
}
// Functions for edge management
bool isConnected() const {
return edges.len() != 0;
}
bool isConnectedWithVertex(const Vertex* const other_vertex) const {
if (other_vertex != nullptr and other_vertex != this)
return edges.includes(other_vertex) != -1;
return false;
}
bool isConnectedWithVertex(const Vertex& other_vertex) const {
return isConnectedWithVertex(&other_vertex);
}
void connectTo(const Vertex* const other_vertex) {
if (other_vertex != nullptr and other_vertex != this) {
if (edges.includes(other_vertex) == -1)
edges.append(other_vertex);
}
}
void disconnectFrom(const Vertex* const other_vertex) {
if (other_vertex != nullptr and other_vertex != this) {
int i = edges.includes(other_vertex);
if (i != -1)
edges.remove(i);
}
}
void connectTo(const Vertex& other_vertex) {
connectTo(&other_vertex);
}
void disconnectFrom(const Vertex& other_vertex) {
disconnectFrom(&other_vertex);
}
// Vertex Destructor
~Vertex() {
data.~T();
for (typename List<Vertex*>::Iterator connected_vertex(edges); not connected_vertex.hasEnded(); connected_vertex++)
connected_vertex()->disconnectFrom(this);
edges.~List();
}
};
List<Vertex> vertices;
Graph() : vertices() {}
Graph(Graph& old_graph) : vertices(old_graph.vertices) {}
void makeVertex(const typename graph_h::AddConstToType<T>::type& data) {
vertices.append(Vertex(data));
}
void removeVertex(unsigned int index) {
vertices.remove(index);
}
void connectVertices(unsigned int i_1, unsigned int i_2) {
Vertex* v_1 = &(vertices[i_1]);
Vertex* v_2 = &(vertices[i_2]);
if (v_1 == v_2 or v_1 == nullptr or v_2 == nullptr)
return;
v_1->connectTo(v_2);
v_2->connectTo(v_1);
}
void disconnectVertices(unsigned int i_1, unsigned int i_2) {
Vertex* v_1 = &(vertices[i_1]);
Vertex* v_2 = &(vertices[i_2]);
if (v_1 == v_2 or v_1 == nullptr or v_2 == nullptr)
return;
v_1->disconnectFrom(v_2);
v_2->disconnectFrom(v_1);
}
void makeConnections(const graph_h::BinaryFunctor<T>& func) {
// Cycle through each possible vertex pair and connect them
// if the given function returns true
for (typename List<Vertex>::Iterator i(vertices); not i.hasEnded(); i++)
for (typename List<Vertex>::Iterator j = i + 1; not j.hasEnded(); j++)
if (func(i().data, j().data)) {
i().connectTo(j());
j().connectTo(i());
}
}
List<List<Vertex*>> colorVertices() {
List<List<Vertex*>> color_list;
for (typename List<Vertex>::Iterator i(vertices); not i.hasEnded(); i++) {
typename List<List<Vertex*>>::Iterator color(color_list);
// Determine if vertex is connected to any other colored vertex
bool is_connected = true;
// Come out color-checking loop if colors have run out or
// the current vertex is not connected to any vertex of the given color
// Don't increment color if none are connected
for (; not (color.hasEnded() or not is_connected); is_connected ? color++ : color)
// Come out of vertex-connection-checking loop
// if there are no more vertices to check or
// the current vertex is connected to any vertex of the given color
for (typename List<Vertex*>::Iterator j( (is_connected = false, color()) ); not (j.hasEnded() or is_connected); ++j)
is_connected |= i().isConnectedWithVertex(j());
// If current vertex was not connected to any in the last color,
// switch back to it
if (color.hasEnded() and not is_connected)
color.last();
if (not color.hasEnded()) {
// Color the vertex if an unconnected color was found
color().append(&i());
} else {
// Make a new color if the vertex is connected to all the current colors
List<Vertex*> new_color;
new_color.append(&i());
color_list.append(new_color);
}
}
return color_list;
}
};
#endif
list.h
#if !defined(VARAD_LIST_H_INCLUDED)
#define VARAD_LIST_H_INCLUDED
namespace list_h {
const int check_num = 0x1234abcd;
// Make type const if it is a pointer
template <class T>
struct AddConstToType {
typedef T type;
};
template <class T>
struct AddConstToType<T*> {
typedef const T* type;
};
template <class T>
class BinaryFunctor {
public :
virtual bool function(const typename AddConstToType<T>::type& a, const typename AddConstToType<T>::type& b) const = 0;
inline bool operator()(const typename AddConstToType<T>::type& a, const typename AddConstToType<T>::type& b) const {
return function(a, b);
}
};
}
template <class T>
class List {
protected :
class Node {
public :
T data;
Node* next;
Node* previous;
const int check;
Node() : check(list_h::check_num) {}
// Weird cast of const T new_data to T so that it can be copied to data
Node(const typename list_h::AddConstToType<T>::type& new_data, Node* prev, Node* nxt = nullptr)
: check(list_h::check_num), data((T)new_data), previous(prev), next(nxt) {}
Node(Node* prev) : check(list_h::check_num), previous(prev), next(nullptr) {}
Node(const Node& old_node, Node* prev = nullptr) : Node(old_node.data, prev) {}
~Node() {
data.~T();
next = previous = nullptr;
}
bool isOk() const {
return check == list_h::check_num;
}
Node* makeNext(const typename list_h::AddConstToType<T>::type& new_data) {
Node* new_next = new Node(new_data, this);
if (new_next != nullptr) next = new_next;
return new_next;
}
Node* makeNext() {
Node* new_next = new Node(this);
if (new_next != nullptr) next = new_next;
return new_next;
}
};
Node* first;
Node* last;
unsigned int length;
Node* getNode(int index) const {
if (length == 0)
index = 0;
else if (index < 0)
index = index % length + length;
else
index = index % length;
Node* cur;
if (2 * index < length) {
cur = first;
for (int i = 1; i <=index; i++) {
cur = cur->next;
}
} else {
cur = last;
for (int i = length - 2; i >= index; i--) {
cur = cur->previous;
}
}
return cur;
}
void reCalculateLength() {
length = 0;
for(Node* read_head = first; read_head != nullptr; read_head = read_head->next, ++length);
}
public:
class Iterator {
friend class List;
const List& parent_list;
Node* current;
public :
int position;
private :
// Custom Constructor for exact copying
Iterator(const List& pops, Node* cur, int pos) : parent_list(pops), current(cur), position(pos) {}
public :
// Basic functions
void first() {
current = parent_list.first;
position = 0;
}
void last() {
current = parent_list.last;
position = parent_list.length - 1;
}
Iterator(const List& papa, bool start_at_first = true) : parent_list(papa) {
if (start_at_first)
first();
else
last();
};
// Constructors
Iterator(const Iterator& i) : parent_list(i.parent_list), current(i.current), position(i.position) {}
Iterator& operator=(const Iterator& i) {
if (&parent_list == &(i.parent_list)) {
current = i.current;
position = i.position;
}/* else {
~Iterator();
new(this) Iterator(i);
}*/
return *this;
}
bool hasEnded() const {
return current == nullptr or position >= parent_list.length or position < 0;
}
bool operator==(const Node* const node) const {
return current == node;
}
bool operator==(const Iterator& i) const {
return current == i.current;
}
bool operator!=(const Node* const node) const {
return not current == node;
}
bool operator!=(const Iterator& i) const {
return not current == i.current;
}
Iterator& operator++() {
if (current != nullptr) current = current->next;
++position;
return *this;
}
Iterator& operator++(int) {
return ++(*this);
}
inline Iterator& operator--() {
if (current != nullptr) current = current->previous;
--position;
return *this;
}
inline Iterator& operator--(int) {
return --(*this);
}
Iterator operator+(int index) {
Node* new_current = current;
if (index < 0)
for(int i = index; i < 0; i++) {
new_current = new_current->previous;
if (new_current == nullptr)
break;
}
else
for (int i = index; i > 0; i--) {
new_current = new_current->next;
if (new_current == nullptr)
break;
}
return Iterator(parent_list, new_current, position + index);
}
inline Iterator operator-(int i) {
return *this + (-i);
}
T& operator()() const {
return current->data;
}
};
// Basic Functions
inline unsigned int len() const {
return length;
}
T& operator[] (int index) const {
return getNode(index)->data;
}
bool append(const typename list_h::AddConstToType<T>::type& new_data) {
if (last == nullptr) {
last = new Node(new_data, nullptr);
first = last;
if (last == nullptr)
return false;
} else {
Node* new_last = last->makeNext(new_data);
if (new_last == nullptr) return false;
last = new_last;
}
++length;
return true;
}
bool prepend(const typename list_h::AddConstToType<T>::type& new_data) {
if (last == nullptr) {
first = last = new Node(new_data, nullptr);
if (first == nullptr)
return false;
} else {
Node* new_node = new Node(new_data, nullptr);
if (new_node == nullptr)
return false;
new_node->next = first;
first->previous = new_node;
first = new_node;
}
++length;
return true;
}
private :
void removeByPointer(Node* node) {
if (node == nullptr)
return;
// Neighbour management
if (node->next != nullptr) node->next->previous = node->previous;
if (node->previous != nullptr) node->previous->next = node->next;
// Boundary conditions
if (node == first)
first = node->next;
if (node == last)
last = node->previous;
// Guillotine
delete node;
--length;
}
public :
void remove(unsigned int index) {
removeByPointer(getNode(index));
}
void remove(Iterator iter) {
if (not iter.hasEnded())
removeByPointer(iter.current);
}
// Constructors
List() : first(nullptr), last(nullptr), length(0) {}
List(const T* const data_arr, unsigned int len) : List() {
for (int i = 0; i < len; ++i)
if (not append(data_arr[i]))
break;
}
List(unsigned int len) : List() {
length = len;
first = new Node(nullptr);
if (first == nullptr)
return;
last = first;
for (int i = 1; i < length; ++i) {
Node* new_last = last->makeNext();
if (new_last == nullptr)
i = length;
else
last = new_last;
}
}
List& operator=(const List& old_list) {
length = old_list.length;
if (length != 0) {
for (Iterator i(old_list); not i.hasEnded(); ++i)
if (not append(i()))
break;
} else {
first = last = nullptr;
}
return *this;
}
List(const List& old_list) : List() {
*this = old_list;
}
~List() {
if (length != 0) {
for (Iterator i(*this, false); not i.hasEnded(); ) {
Node* current = i.current;
--i;
delete current;
}
first = last = nullptr;
}
}
bool insert(unsigned int index, const typename list_h::AddConstToType<T>::type& new_data) {
if (index == length)
return append(new_data);
else if (index == 0)
return prepend(new_data);
Node* cur = getNode(index);
if (cur == nullptr)
return false;
Node* new_node = new Node(new_data, nullptr);
if (new_node == nullptr)
return false;
new_node->next = cur;
new_node->previous = cur->previous;
if (new_node->previous != nullptr) new_node->previous->next = new_node;
cur->previous = new_node;
++length;
}
void truncate(int end) {
// Domain folding
if (length == 0)
end = 0;
else if (end < 0)
end = end % length + length;
else
end = end % length;
Iterator i(*this);
for ( i.last(); not i.hasEnded() and i.position >= end; ) {
Node* current = i.current;
--i;
delete current;
}
// Loose ends management
last = i.current;
if (i.current != nullptr) i.current->next = nullptr;
length = end;
}
void slice(int start, int end, unsigned int step = 1) {
// Domain folding
if (length == 0)
end = 0;
else if (end < 0)
end = end % length + length;
else
end = end % length;
if (length == 0)
start = 0;
else if (start < 0)
start = start % length + length;
else
start = start % length;
if (start >= end)
return;
truncate(end);
Iterator i(*this);
for ( i.first(); not i.hasEnded() and i.position < start; ) {
Node* current = i.current;
++i;
delete current;
}
// Loose ends management
first = i.current;
if (i.current != nullptr) i.current->prev = nullptr;
length -= start;
// No iterators here because the list is dynamically changing
if (step != 1 or step != 0) {
for (int i = 0, offset = 0; i < length; ++i) {
if ((i + offset) % step != 0) {
remove(i);
++offset; --i;
}
}
reCalculateLength();
}
}
void swap(unsigned int i_1, unsigned int i_2) {
Node* temp;
// Ensure that i_1 <= i_2
if (i_1 > i_2) {
unsigned int temp = i_1;
i_1 = i_2;
i_2 = temp;
}
if (i_1 == i_2) return;
Node* node_1 = getNode(i_1);
Node* node_2 = getNode(i_2);
if (node_1 == nullptr or node_2 == nullptr)
return;
// Check if list markers need to be changed
if (i_1 == 0)
first = node_2;
else if (i_1 == length - 1)
last = node_2;
if (i_2 == 0)
first = node_1;
else if (i_2 == length - 1)
last = node_1;
// Alternate behaviour for adjacent swaps
if (i_1 + 1 == i_2) {
node_1->next = node_2->next;
node_2->previous = node_1->previous;
node_1->previous = node_2;
node_2->next = node_1;
if (node_1->next != nullptr) node_1->next->previous = node_1;
if (node_2->previous != nullptr) node_2->previous->next = node_2;
return;
}
// Standard swap behaviour
temp = node_1->previous;
node_1->previous = node_2->previous;
node_2->previous = temp;
temp = node_1->next;
node_1->next = node_2->next;
node_2->next = temp;
// Correct neighbours addresses
if (node_1->next != nullptr) node_1->next->previous = node_1;
if (node_1->previous != nullptr) node_1->previous->next = node_1;
if (node_2->next != nullptr) node_2->next->previous = node_2;
if (node_2->previous != nullptr) node_2->previous->next = node_2;
}
void sort(const list_h::BinaryFunctor<T>& func) {
class Dummy {
public :
void quickSort(List& arr, unsigned int start, unsigned int end, const list_h::BinaryFunctor<T>& func) {
if (end - start <= 1)
return;
int pivotIndex = start;
for (int i = start; i < end - 1; ++i) {
if (func(arr[i], arr[end - 1])) {
arr.swap(i, pivotIndex);
++pivotIndex;
}
}
arr.swap(pivotIndex, end - 1);
quickSort(arr, start, pivotIndex, func);
quickSort(arr, pivotIndex + 1, end, func);
}
} dummy_dum_dum;
dummy_dum_dum.quickSort(*this, 0, length, func);
}
int includes(const typename list_h::AddConstToType<T>::type& search_data) const {
for (Iterator i(*this); not i.hasEnded(); i++)
if (i() == search_data and i.position < length)
return i.position;
return -1;
}
};
#endif
List
, and one for aGraph
implementation that usesstd::list
. \$\endgroup\$c++
) in addition to the specific versionc++11
. This is because tags are used for searching and many other related mechanisms. For example, people who follow thec++
tag may miss your question if you tagc++11
only. (I should have updated my watch list toc++*
!) 2) Did you compile your code using Turbo-C++ or a modern compiler? Please clarify in your question. 3) Your github link gives me Page not found ... Make sure you've made your project public. \$\endgroup\$