init lab 3

This commit is contained in:
Trygve 2024-03-09 16:44:29 +01:00
parent 7b40a4243d
commit 2314cf70aa
9 changed files with 814 additions and 0 deletions

View File

@ -0,0 +1,21 @@
binary = sequence-test
folder = sequences-int
objects = $(patsubst %.cpp,%.o,$(wildcard *.cpp))
run: $(binary)
./$(binary)
$(binary): $(objects)
g++ -g3 -o $@ $^
%.o: %.cpp
g++ -g3 -c -o $@ $<
clean:
rm -f *.o
clear: clean
rm -f *.zip $(binary)
zip: clean
zip $(folder) Makefile *.cpp *.h

View File

@ -0,0 +1,164 @@
#include "doubly-linked-list.h"
using namespace seq;
// return the size (number of items in the doubly linked list)
size_t DoublyLinkedList::size() const
{
size_t count = 0;
for(DoublyLinkedListNode* n = this->head; n != nullptr; n = n->get_next()) count++;
return count;
}
// return pointer to the node at position i, counting from 0
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
DoublyLinkedListNode* DoublyLinkedList::index(int i) const
{
DoublyLinkedListNode* n = this->head;
if(i > 0)
{
for(int k = 0; k < i; k++)
{
assert(n != nullptr);
n = n->get_next();
}
}
else /** in the doubly linked list, we can walk backward, so let us allow it for negative i **/
{
DoublyLinkedListNode* n = this->tail;
for(int k = -1; k > i; k--)
{
assert(n != nullptr);
n = n->get_prev();
}
}
return n;
}
// add an item at the end of the list
void DoublyLinkedList::push_back(const int& pushed_item)
{
DoublyLinkedListNode* new_node = new DoublyLinkedListNode;
new_node->set_item(pushed_item);
if(this->empty()) this->head = new_node;
else
{
this->tail->set_next(new_node);
new_node->set_prev(this->tail); /** attachment in the doubly linked list goes both ways **/
}
this->tail = new_node;
}
// add an item at the beginning of the list
void DoublyLinkedList::push_front(const int& pushed_item)
{
DoublyLinkedListNode* new_node = new DoublyLinkedListNode;
new_node->set_item(pushed_item);
if(this->empty()) this->tail = new_node;
else
{
new_node->set_next(this->head); // FIX BUG from the previous version, which was "new_node->set_next(this->head->get_next());"
this->head->set_prev(new_node); // FIX BUG from the previous version, which was "this->head->get_next()->set_prev(new_node);"
}
this->head = new_node;
}
// remove the head node and item
void DoublyLinkedList::pop_front()
{
if(this->empty()) return; // nothing there to remove
DoublyLinkedListNode* successor = this->head->get_next();
if(successor) successor->set_prev(nullptr); /** we must detach the previous head node from its successor **/
delete this->head;
this->head = successor; // successor of the previous head is the new head
if(this->head == nullptr) this->tail = nullptr; // catch special case: the list is now empty
}
// remove the tail node and item
/** note how this is much easier for the doubly than for the singly linked list; **
** we can simply take the "pop_front()" implementation and do everything symmetrically **/
void DoublyLinkedList::pop_back()
{
if(this->empty()) return; // nothing there to remove
DoublyLinkedListNode* predecessor = this->tail->get_prev();
if(predecessor) predecessor->set_next(nullptr);
delete this->tail;
this->tail = predecessor; /** predecessor of the previous tail is the new tail **/
if(this->tail == nullptr) this->head = nullptr; /** catch special case: the list is now empty **/
}
// insert an item at index i
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
void DoublyLinkedList::insert_at(int i, const int& inserted_item)
{
if(i == 0)
{
this->push_front(inserted_item);
return;
}
else if(i == -1)
{
this->push_back(inserted_item);
return;
}
DoublyLinkedListNode* predecessor = this->index(i-1);
this->insert_successor_to(predecessor, inserted_item);
}
// remove the item at index i
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
void DoublyLinkedList::erase_at(int i)
{
if(i == 0)
{
this->pop_front();
return;
}
else if(i == -1)
{
this->pop_back();
return;
}
DoublyLinkedListNode* erased_node = this->index(i);
this->erase_node(erased_node);
}
// insert an item after given node
void DoublyLinkedList::insert_successor_to(DoublyLinkedListNode* predecessor, const int& inserted_item)
{
DoublyLinkedListNode* new_node = new DoublyLinkedListNode;
new_node->set_item(inserted_item);
DoublyLinkedListNode* successor = predecessor->get_next();
predecessor->set_next(new_node);
new_node->set_prev(predecessor); /** attach both ways **/
new_node->set_next(successor);
if(!successor) this->tail = new_node;
else successor->set_prev(new_node); /** attach both ways **/
}
/** remove "erased_node" from the doubly linked list **/
void DoublyLinkedList::erase_node(DoublyLinkedListNode* erased_node)
{
if(erased_node->get_prev() == nullptr) /** erase the head **/
{
this->pop_front();
return;
}
if(erased_node->get_next() == nullptr) /** erase the tail **/
{
this->pop_back();
return;
}
/** now attach predecessor and successor to each other **/
erased_node->get_prev()->set_next(erased_node->get_next());
erased_node->get_next()->set_prev(erased_node->get_prev());
delete erased_node; /** "erased_node" detached now, we can delete it **/
}

View File

@ -0,0 +1,104 @@
#ifndef DOUBLY_LINKED_LIST_H
#define DOUBLY_LINKED_LIST_H
#include <cassert>
#include "sequence.h"
namespace seq
{
class DoublyLinkedListNode
{
public:
// return a reference to the stored item
int& get_item() { return this->item; }
// return pointer to next node, or nullptr if this is the final node
DoublyLinkedListNode* get_next() const { return this->next; }
// return pointer to previous node, or nullptr if this is the initial node
DoublyLinkedListNode* get_prev() const { return this->prev; }
// overwrite the item stored in this node
void set_item(int in_item) { this->item = in_item; }
private:
int item = 0;
DoublyLinkedListNode* next = nullptr;
DoublyLinkedListNode* prev = nullptr;
// attach a node behind this node
// if there was a node attached to this previously, it is NOT deleted!
void set_next(DoublyLinkedListNode* in_next) { this->next = in_next; }
// attach a node before this node
// if there was a node attached to this previously, it is NOT deleted!
void set_prev(DoublyLinkedListNode* in_prev) { this->prev = in_prev; }
friend class DoublyLinkedList; // allow DoublyLinkedList to access private members
};
class DoublyLinkedList: public Sequence
{
public:
bool empty() const { return (this->head == nullptr); } // test whether the doubly linked list is empty
size_t size() const; // return the size (number of items in the doubly linked list)
// it is the caller's responsibility to ensure that the list is not empty when calling front() or back()!
int& front() { assert(this->head); return this->head->get_item(); } // return a reference to the first item
int& back() { assert(this->tail); return this->tail->get_item(); } // return a reference to the final item
// return a reference to the item at position i of the list, counting from 0
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
// it is the caller's responsibility that the index is within range
int& at(int i) { return this->index(i)->get_item(); }
// return pointer to the head/tail node
DoublyLinkedListNode* begin() const { return this->head; }
DoublyLinkedListNode* end() const { return this->tail; }
// return pointer to the node at position i, counting from 0
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
DoublyLinkedListNode* index(int i) const;
/*
* accepts an additional item into the doubly linked list;
* by default, this is done at the back end of the list
* call push_front(...) to push an element at the front
*
* the list takes ownership of the copy (but not of the original!)
*/
void push(const int& pushed_item) { this->push_back(pushed_item); }
void push_back(const int& pushed_item);
void push_front(const int& pushed_item);
/*
* removes an item from the list (front end by default)
* to do the same at the back, call pop_back()
*/
void pop() { this->pop_front(); }
void pop_front();
void pop_back();
void clear() { while(!this->empty()) this->pop(); } // remove all the items from the list
// it is the caller's responsibility that the index is within range
// for negative numbers, count from the tail (-1) backward (-2, -3, ...)
void insert_at(int i, const int& inserted_item); // insert an item at index i
void erase_at(int i); // remove the item at index i
// it is the caller's responsibility that the node is actually part of the list
void insert_successor_to(DoublyLinkedListNode* predecessor, const int& inserted_item); // insert an item after given node
void erase_successor_to(DoublyLinkedListNode* predecessor) // remove the item after given node
{
this->erase_node(predecessor->get_next());
}
void erase_node(DoublyLinkedListNode* erased_node);
~DoublyLinkedList() { this->clear(); }
private:
DoublyLinkedListNode* head = nullptr;
DoublyLinkedListNode* tail = nullptr;
};
}
#endif

View File

@ -0,0 +1,95 @@
#include <algorithm>
#include <cassert>
#include "dynamic-array.h"
using namespace seq;
// remove all the items from the array
void DynamicArray::clear()
{
if(this->values) delete this->values;
this->values = nullptr;
this->logical_size = 0;
this->capacity = 0;
}
// insert an item at index i
void DynamicArray::insert_at(int i, const int& inserted_item)
{
assert((i >= 0) && (this->logical_size >= i));
// catch the case where capacity is exhausted and we need to allocate more memory
if(this->logical_size == this->capacity)
{
size_t new_capacity = 2 * this->capacity;
if(new_capacity == 0) new_capacity = 1;
this->resize(new_capacity);
}
assert(this->capacity > this->logical_size);
// shift all elements from index i onward one to the right
// we use a temporary storage and copy() from <algorithm> to do this efficiently
size_t shifted_elements = this->logical_size - i;
if(shifted_elements > 0)
{
int* temp_storage = new int[shifted_elements]();
// copy values[i] to values[i+shifted_elements-1] into temp_storage
std::copy(this->values + i, this->values + i + shifted_elements, temp_storage);
// copy all of temp_storage into values[i+1] to values[logical_size]
std::copy(temp_storage, temp_storage + shifted_elements, &this->values[i+1]);
delete temp_storage;
}
// now we can write the inserted item into values[i]
this->logical_size++;
this->values[i] = inserted_item;
}
// remove the item at index i
void DynamicArray::erase_at(int i)
{
assert((i >= 0) && this->logical_size > i);
// shift all elements from index i+1 onward one to the left
// we use a temporary storage and copy() from <algorithm> to do this efficiently
size_t shifted_elements = this->logical_size - i - 1;
if(shifted_elements > 0)
{
int* temp_storage = new int[shifted_elements]();
// copy values[i+1] to values[logical_size-1] into temp_storage
std::copy(this->values + i+1, this->values + this->logical_size, temp_storage);
// copy all of temp_storage into values[i] to values[logical_size-2]
std::copy(temp_storage, temp_storage + shifted_elements, &this->values[i]);
delete temp_storage;
}
this->logical_size--; // with this, we are done with the task
// now let us see whether we have deleted so many items that we should resize to save memory
if(this->capacity/2 >= this->logical_size) this->resize(this->capacity/2);
}
// allocate static array with "new_capacity" elements,
// copy the contents there, and delete the previous static array
void DynamicArray::resize(size_t new_capacity)
{
assert(new_capacity >= this->logical_size);
int* allocated_memory = new int[new_capacity]();
if(this->values != nullptr)
{
// use <algorithm> library construct for efficient memory-level copying
// take values[0] to values[logical-size-1] and copy it into allocated_memory
std::copy(this->values, this->values + this->logical_size, allocated_memory);
delete this->values; // now we can delete the old storage
}
// update class properties
this->values = allocated_memory;
this->capacity = new_capacity;
}

View File

@ -0,0 +1,58 @@
#include "sequence.h"
namespace seq
{
class DynamicArray: public Sequence
{
public:
bool empty() const { return (this->logical_size == 0); } // test whether the array is empty
size_t size() const { return this->logical_size; } // return the logical size (number of items in the array)
// it is the caller's responsibility to ensure that the array is not empty!
int& front() { return this->values[0]; } // return a reference to the first item
int& back() { return this->values[this->logical_size - 1]; } // return a reference to the final item
// return a reference to the item at position i of the sequence, counting from 0
// we use modulo arithmetics to avoid over-/underflow; will still fail for an empty array
int& at(int i) { return this->values[i % this->logical_size]; }
/*
* accepts an additional item into the dynamic array;
* by default, this is done at the back end of the array
* call push_front(...) to push an element at the front
*
* the array takes ownership of the copy (but not of the original!)
*/
void push(const int& pushed_item) { this->push_back(pushed_item); }
void push_back(const int& pushed_item) { this->insert_at(this->logical_size, pushed_item); }
void push_front(const int& pushed_item) { this->insert_at(0, pushed_item); }
/*
* removes an item from the list (back end by default)
* to do the same at the front, call pop_front()
*/
void pop() { this->pop_back(); }
void pop_front() { this->erase_at(0); }
void pop_back() { this->erase_at(this->logical_size - 1); }
void clear(); // remove all the items from the array
void insert_at(int i, const int& inserted_item); // insert an item at index i
void erase_at(int i); // remove the item at index i
// overwrite the element at index i
// we use modulo arithmetics to avoid over-/underflow; will still fail for an empty array
void set_value_at(int i, const int& in_item) { this->values[i % this->logical_size] = in_item; }
~DynamicArray() { this->clear(); }
private:
// this is a static C/C++ array containing the actual data
// it is owned by the dynamic array
int* values = nullptr;
size_t logical_size = 0; // how many data items are we actually storing?
size_t capacity = 0; // how much memory did we allocate?
void resize(size_t new_capacity); // shift to static array with increased/decreased capacity
};
}

View File

@ -0,0 +1,101 @@
#include <cassert>
#include <chrono>
#include <iostream>
#include "dynamic-array.h"
#include "singly-linked-list.h"
#include "doubly-linked-list.h"
namespace
{
/*
* run a simple test
*/
void test_sequence(seq::Sequence* sqn, int n, int m, std::ostream* os)
{
assert((m > 0) && (n > m));
if(os) *os << "Push even numbers from 0 to " << 2*(n-1) << ".\n";
for(int i = 0; i < n; i++) sqn->push(2*i);
if(os) *os << "Overwrite element at index " << m/2 << " with 0.\n";
int& item = sqn->at(m/2);
item = 0;
if(os) *os << "\nSize of sequence: " << sqn->size() << ".\n";
if(os) *os << "Detaching " << m/2 << " elements from front:";
for(int i = 0; i < m/2; i++)
{
if(os) *os << " " << sqn->front();
sqn->pop_front();
}
if(os) *os << ".\nDetaching " << m/2 << " elements from back:";
for(int i = 0; i < m/2; i++)
{
if(os) *os << " " << sqn->back();
sqn->pop_back();
}
if(os) *os << ".\nSize of sequence: " << sqn->size() << ".\n";
if(os) *os << "\nElement at index " << (n-m)/3 << ": " << sqn->at((n-m)/3) << ".\n";
if(os) *os << "Element at index " << 2*(n-m)/3 << ": " << sqn->at(2*(n-m)/3) << ".\n";
if(os) *os << "\nInsert " << m << " at index " << 2*(n-m)/3 << ".\n";
sqn->insert_at(2*(n-m)/3, m);
if(os) *os << "Size of sequence: " << sqn->size() << ".\n";
if(os) *os << "\nDelete element at index " << (n-m)/3 << ".\n";
sqn->erase_at((n-m)/3);
if(os) *os << "Size of sequence: " << sqn->size() << ".\n";
if(os) *os << "Element at index " << 2*(n-m)/3 - 1 << ": " << sqn->at(2*(n-m)/3 - 1) << ".\n";
if(os) *os << "\nClearing.\n";
sqn->clear();
if(os) *os << "Size of sequence: " << sqn->size() << ".\n";
}
/*
* return time measurement in units of seconds
*/
float test_with_time_measurement(seq::Sequence* sqn, int iterations)
{
int sequence_length = 200001;
int deletions = 10;
test_sequence(sqn, sequence_length, deletions, &std::cout);
int log_entries = 10;
std::cout << "\nNow repeat the above " << iterations << " times:\n";
auto t0 = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
{
test_sequence(sqn, sequence_length, deletions, nullptr);
if((i+1) % (iterations/log_entries) == 0)
{
std::cout << "\t" << i+1 << "\n";
std::cout.flush(); // make sure that status output is shown without delay
}
}
auto t1 = std::chrono::high_resolution_clock::now();
return 1.0e-06 * std::chrono::duration_cast<std::chrono::microseconds>(t1-t0).count();
}
}
int main()
{
int iterations = 200;
std::cout << "*** test with dynamic array ***\n";
seq::DynamicArray dyna;
float dyna_time = test_with_time_measurement(&dyna, iterations);
std::cout << "\n\n*** test with singly linked list ***\n";
seq::SinglyLinkedList sll;
float sll_time = test_with_time_measurement(&sll, iterations);
std::cout << "\n\n*** test with doubly linked list ***\n";
seq::DoublyLinkedList dll;
float dll_time = test_with_time_measurement(&dll, iterations);
std::cout << "\n\nRuntime for dynamic array:\t" << dyna_time << " s\n";
std::cout << "Runtime for singly linked list:\t" << sll_time << " s\n";
std::cout << "Runtime for doubly linked list:\t" << dll_time << " s\n";
}

View File

@ -0,0 +1,42 @@
#ifndef SEQUENCE_H
#define SEQUENCE_H
#include <iostream>
namespace seq
{
class Sequence
{
public:
virtual bool empty() const = 0; // test whether the sequence is empty
virtual size_t size() const = 0; // return the size (number of items in the sequence)
// it is the caller's responsibility to ensure that the sequence is not empty when calling front() or back()!
virtual int& front() = 0; // return a reference to the first item
virtual int& back() = 0; // return a reference to the final item
// return a reference to the item at position i of the sequence, counting from 0
// it is the caller's responsibility that the index is within range
virtual int& at(int i) = 0;
/*
* accepts an additional item into the sequence (front or back);
* the pushed item is passed by reference, but a copy of it is stored in the Sequence
* the Sequence takes ownership of the copy (but not of the original!)
*/
virtual void push(const int& pushed_item) = 0; // default push operation (front or back)
virtual void push_front(const int& pushed_item) = 0;
virtual void push_back(const int& pushed_item) = 0;
virtual void pop() = 0; // default pop operation (front or back)
virtual void pop_front() = 0; // remove the first element
virtual void pop_back() = 0; // remove the last element
virtual void clear() = 0; // remove all the items from the sequence
// it is the caller's responsibility that the index is within range
virtual void insert_at(int i, const int& inserted_item) = 0; // insert an item at index i
virtual void erase_at(int i) = 0; // remove the item at index i
};
}
#endif

View File

@ -0,0 +1,140 @@
#include "singly-linked-list.h"
using namespace seq;
// return the size (number of items in the singly linked list)
size_t SinglyLinkedList::size() const
{
size_t count = 0;
for(SinglyLinkedListNode* n = this->head; n != nullptr; n = n->get_next()) count++;
return count;
}
// return pointer to the node at position i, counting from 0
SinglyLinkedListNode* SinglyLinkedList::index(int i) const
{
SinglyLinkedListNode* n = this->head;
for(int k = 0; k < i; k++)
{
assert(n != nullptr);
n = n->get_next();
}
return n;
}
// add an item at the end of the list
void SinglyLinkedList::push_back(const int& pushed_item)
{
SinglyLinkedListNode* new_node = new SinglyLinkedListNode;
new_node->set_item(pushed_item);
if(this->empty()) this->head = new_node;
else this->tail->set_next(new_node);
this->tail = new_node;
}
// add an item at the beginning of the list
void SinglyLinkedList::push_front(const int& pushed_item)
{
SinglyLinkedListNode* new_node = new SinglyLinkedListNode;
new_node->set_item(pushed_item);
if(this->empty()) this->tail = new_node;
else new_node->set_next(this->head); // FIG BUG from previous version, which was "else new_node->set_next(this->head->get_next());"
this->head = new_node;
}
// remove the head node and item
void SinglyLinkedList::pop_front()
{
if(this->empty()) return; // nothing there to remove
SinglyLinkedListNode* successor = this->head->get_next();
delete this->head;
this->head = successor; // successor of the previous head is the new head
if(this->head == nullptr) this->tail = nullptr; // catch special case: the list is now empty
}
// remove the tail node and item
// note: in a singly linked list, we need to walk step by step to the end!
void SinglyLinkedList::pop_back()
{
if(this->empty()) return; // nothing there to remove
// list only contains one element
if(this->head->get_next() == nullptr)
{
delete this->head;
this->head = nullptr;
this->tail = nullptr;
return;
}
/*
* walk through until "third" is nullptr, second is the last element, and first is the second-to-last
*/
SinglyLinkedListNode* first = this->head;
SinglyLinkedListNode* second = first->get_next();
SinglyLinkedListNode* third = second->get_next();
while(third != nullptr)
{
first = second;
second = third;
third = third->get_next();
}
/*
* remove "second"
*/
if(this->tail == second) this->tail = first;
first->set_next(nullptr);
delete second;
}
// insert an item at index i
void SinglyLinkedList::insert_at(int i, const int& inserted_item)
{
if(i == 0)
{
this->push_front(inserted_item);
return;
}
SinglyLinkedListNode* predecessor = this->index(i-1);
this->insert_successor_to(predecessor, inserted_item);
}
// remove the item at index i
void SinglyLinkedList::erase_at(int i)
{
if(i == 0)
{
this->pop_front();
return;
}
SinglyLinkedListNode* predecessor = this->index(i-1);
this->erase_successor_to(predecessor);
}
// insert an item after given node
void SinglyLinkedList::insert_successor_to(SinglyLinkedListNode* predecessor, const int& inserted_item)
{
SinglyLinkedListNode* new_node = new SinglyLinkedListNode;
new_node->set_item(inserted_item);
SinglyLinkedListNode* successor = predecessor->get_next();
predecessor->set_next(new_node);
new_node->set_next(successor);
if(!successor) this->tail = new_node;
}
// remove the item after given node
void SinglyLinkedList::erase_successor_to(SinglyLinkedListNode* predecessor)
{
SinglyLinkedListNode* erased_node = predecessor->get_next();
SinglyLinkedListNode* new_successor = erased_node->get_next();
predecessor->set_next(new_successor);
if(new_successor == nullptr) this->tail = predecessor;
delete erased_node;
}

View File

@ -0,0 +1,89 @@
#ifndef SINGLY_LINKED_LIST_H
#define SINGLY_LINKED_LIST_H
#include <cassert>
#include "sequence.h"
namespace seq
{
class SinglyLinkedListNode
{
public:
// return a reference to the stored item
int& get_item() { return this->item; }
// return pointer to next node, or nullptr if this is the final node
SinglyLinkedListNode* get_next() const { return this->next; }
// overwrite the item stored in this node
void set_item(int in_item) { this->item = in_item; }
private:
int item = 0;
SinglyLinkedListNode* next = nullptr;
// attach a node to this node
// if there was a node attached to this previously, it is NOT deleted!
void set_next(SinglyLinkedListNode* in_next) { this->next = in_next; }
friend class SinglyLinkedList; // allow SinglyLinkedList to access private members
};
class SinglyLinkedList: public Sequence
{
public:
bool empty() const { return (this->head == nullptr); } // test whether the singly linked list is empty
size_t size() const; // return the size (number of items in the singly linked list)
// it is the caller's responsibility to ensure that the list is not empty when calling front() or back()!
int& front() { assert(this->head); return this->head->get_item(); } // return a reference to the first item
int& back() { assert(this->tail); return this->tail->get_item(); } // return a reference to the final item
// return a reference to the item at position i of the list, counting from 0
// it is the caller's responsibility that the index is within range
int& at(int i) { return this->index(i)->get_item(); }
// return pointer to the head/tail node
SinglyLinkedListNode* begin() const { return this->head; }
SinglyLinkedListNode* end() const { return this->tail; }
// return pointer to the node at position i, counting from 0
SinglyLinkedListNode* index(int i) const;
/*
* accepts an additional item into the singly linked list;
* by default, this is done at the back end of the list
* call push_front(...) to push an element at the front
*
* the list takes ownership of the copy (but not of the original!)
*/
void push(const int& pushed_item) { this->push_back(pushed_item); }
void push_back(const int& pushed_item);
void push_front(const int& pushed_item);
/*
* removes an item from the list (front end by default)
* to do the same at the back, call pop_back()
*/
void pop() { this->pop_front(); }
void pop_front();
void pop_back();
void clear() { while(!this->empty()) this->pop(); } // remove all the items from the list
// it is the caller's responsibility that the index is within range
void insert_at(int i, const int& inserted_item); // insert an item at index i
void erase_at(int i); // remove the item at index i
// it is the caller's responsibility that the node is actually part of the list
void insert_successor_to(SinglyLinkedListNode* predecessor, const int& inserted_item); // insert an item after given node
void erase_successor_to(SinglyLinkedListNode* predecessor); // remove the item after given node
~SinglyLinkedList() { this->clear(); }
private:
SinglyLinkedListNode* head = nullptr;
SinglyLinkedListNode* tail = nullptr;
};
}
#endif