From 2314cf70aafa4efa6dfe4d3f583485f04ef08b99 Mon Sep 17 00:00:00 2001 From: Trygve Date: Sat, 9 Mar 2024 16:44:29 +0100 Subject: [PATCH] init lab 3 --- lab_3/sequences-int/Makefile | 21 +++ lab_3/sequences-int/doubly-linked-list.cpp | 164 +++++++++++++++++++++ lab_3/sequences-int/doubly-linked-list.h | 104 +++++++++++++ lab_3/sequences-int/dynamic-array.cpp | 95 ++++++++++++ lab_3/sequences-int/dynamic-array.h | 58 ++++++++ lab_3/sequences-int/sequence-test.cpp | 101 +++++++++++++ lab_3/sequences-int/sequence.h | 42 ++++++ lab_3/sequences-int/singly-linked-list.cpp | 140 ++++++++++++++++++ lab_3/sequences-int/singly-linked-list.h | 89 +++++++++++ 9 files changed, 814 insertions(+) create mode 100644 lab_3/sequences-int/Makefile create mode 100644 lab_3/sequences-int/doubly-linked-list.cpp create mode 100644 lab_3/sequences-int/doubly-linked-list.h create mode 100644 lab_3/sequences-int/dynamic-array.cpp create mode 100644 lab_3/sequences-int/dynamic-array.h create mode 100644 lab_3/sequences-int/sequence-test.cpp create mode 100644 lab_3/sequences-int/sequence.h create mode 100644 lab_3/sequences-int/singly-linked-list.cpp create mode 100644 lab_3/sequences-int/singly-linked-list.h diff --git a/lab_3/sequences-int/Makefile b/lab_3/sequences-int/Makefile new file mode 100644 index 0000000..fa2a9a4 --- /dev/null +++ b/lab_3/sequences-int/Makefile @@ -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 diff --git a/lab_3/sequences-int/doubly-linked-list.cpp b/lab_3/sequences-int/doubly-linked-list.cpp new file mode 100644 index 0000000..37ab4db --- /dev/null +++ b/lab_3/sequences-int/doubly-linked-list.cpp @@ -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 **/ +} diff --git a/lab_3/sequences-int/doubly-linked-list.h b/lab_3/sequences-int/doubly-linked-list.h new file mode 100644 index 0000000..4b97177 --- /dev/null +++ b/lab_3/sequences-int/doubly-linked-list.h @@ -0,0 +1,104 @@ +#ifndef DOUBLY_LINKED_LIST_H +#define DOUBLY_LINKED_LIST_H + +#include +#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 diff --git a/lab_3/sequences-int/dynamic-array.cpp b/lab_3/sequences-int/dynamic-array.cpp new file mode 100644 index 0000000..068a539 --- /dev/null +++ b/lab_3/sequences-int/dynamic-array.cpp @@ -0,0 +1,95 @@ +#include +#include + +#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 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 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 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; +} diff --git a/lab_3/sequences-int/dynamic-array.h b/lab_3/sequences-int/dynamic-array.h new file mode 100644 index 0000000..e632b30 --- /dev/null +++ b/lab_3/sequences-int/dynamic-array.h @@ -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 + }; +} diff --git a/lab_3/sequences-int/sequence-test.cpp b/lab_3/sequences-int/sequence-test.cpp new file mode 100644 index 0000000..77853c3 --- /dev/null +++ b/lab_3/sequences-int/sequence-test.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#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(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"; +} diff --git a/lab_3/sequences-int/sequence.h b/lab_3/sequences-int/sequence.h new file mode 100644 index 0000000..2151e68 --- /dev/null +++ b/lab_3/sequences-int/sequence.h @@ -0,0 +1,42 @@ +#ifndef SEQUENCE_H +#define SEQUENCE_H + +#include + +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 diff --git a/lab_3/sequences-int/singly-linked-list.cpp b/lab_3/sequences-int/singly-linked-list.cpp new file mode 100644 index 0000000..6f35508 --- /dev/null +++ b/lab_3/sequences-int/singly-linked-list.cpp @@ -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; +} diff --git a/lab_3/sequences-int/singly-linked-list.h b/lab_3/sequences-int/singly-linked-list.h new file mode 100644 index 0000000..434cb7b --- /dev/null +++ b/lab_3/sequences-int/singly-linked-list.h @@ -0,0 +1,89 @@ +#ifndef SINGLY_LINKED_LIST_H +#define SINGLY_LINKED_LIST_H + +#include +#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