Files
Datastructs/Exam/IKT203Exam/LibExample/TCircularDoublyLinkedListTemplate.hpp
2025-11-05 20:13:14 +01:00

455 lines
14 KiB
C++

// Implantation of the Circular Doubly Linked List
#ifndef TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP
#define TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP
#pragma once
#include "TDoublyLinkedListTemplate.hpp"
template <typename T>
class TCircularDoublyLinkedList : public TDoublyLinkedList<T> {
private:
// Helper to remove a node, updating both next and prev pointers
void InternalRemoveNode(TNode<T>* aNodeToDelete);
protected:
// --- New Member Variable ---
TNode<T>* cursor; // Points to the "current" node in the list.
public:
using TSingleLinkedList<T>::Append;
using TSingleLinkedList<T>::Prepend;
// --- Constructor & Destructor ---
TCircularDoublyLinkedList(bool aIsDataOwner);
virtual ~TCircularDoublyLinkedList() override;
// --- New Cursor Management Methods ---
void ResetCursor();
T GetCursorData() const;
void AdvanceCursor(int aSteps = 1);
void RewindCursor(int aSteps = 1);
int GetCursorIndex() const;
// --- Overridden Virtual Methods ---
// For maintaining the circular link
void Append(const T&) override;
void Prepend(const T&) override;
void Remove(const T&) override;
void RemoveAll(const T&) override;
T RemoveLast() override;
void Reverse() override;
void ReverseSublist(int, int) override;
void Merge(TSingleLinkedList<T>& otherList) override;
// For avoiding infinite loops
bool Contains(const T&) const override;
T Search(const T&, FCheckNode<T>) const override;
void ForEach(FVisitNode<T>) const override;
TNode<T>* GetMiddle() const override;
};
// Constructor: Establishes the initial circular link where the head points to itself.
template <typename T>
TCircularDoublyLinkedList<T>::TCircularDoublyLinkedList(bool aIsDataOwner)
: TDoublyLinkedList<T>(aIsDataOwner) {
// An empty circular list's dummy head points to itself.
this->head->SetNext(this->head);
this->head->SetPrev(this->head);
this->cursor = this->head; // Cursor also starts at the head.
}
// Destructor: The parent destructors are virtual and will handle cleanup.
template <typename T>
TCircularDoublyLinkedList<T>::~TCircularDoublyLinkedList() {
// Before the parent destructors run, we must break the circular link.
// The parent's `nullptr`-terminated cleanup loop will now work correctly.
if (!this->IsEmpty()) {
// The tail is the node before the dummy head.
// Set its 'next' pointer to null instead of back to the head.
this->head->GetPrev()->SetNext(nullptr);
}
// Now, the chain is broken: head -> node1 -> ... -> tail -> nullptr
// The TDoublyLinkedList and TSingleLinkedList destructors will be called
// automatically after this, and they will now function correctly.
}
// Moves the cursor to the first element in the list.
template <typename T>
void TCircularDoublyLinkedList<T>::ResetCursor() {
if (this->IsEmpty()) {
this->cursor = this->head;
}
else {
this->cursor = this->head->GetNext();
}
}
// Returns the data at the cursor's current position.
template <typename T>
T TCircularDoublyLinkedList<T>::GetCursorData() const {
// The cursor is only valid if it's not pointing to the dummy head.
if (!this->IsEmpty() && this->cursor != this->head) {
return this->cursor->GetData();
}
return nullptr;
}
// Moves the cursor forward, wrapping around if needed.
template <typename T>
void TCircularDoublyLinkedList<T>::AdvanceCursor(int aSteps) {
if (this->IsEmpty()) {
return;
}
for (int i = 0; i < aSteps; ++i) {
this->cursor = this->cursor->GetNext();
// If we move to the dummy head, we've wrapped around. Skip over it to the first element.
if (this->cursor == this->head) {
this->cursor = this->head->GetNext();
}
}
}
// Moves the cursor backward, wrapping around if needed.
template <typename T>
void TCircularDoublyLinkedList<T>::RewindCursor(int aSteps) {
if (this->IsEmpty()) {
return;
}
for (int i = 0; i < aSteps; ++i) {
this->cursor = this->cursor->GetPrev();
// If we move to the dummy head, we've wrapped around. Skip over it to the last element.
if (this->cursor == this->head) {
this->cursor = this->head->GetPrev();
}
}
}
// Gets the numerical index of the cursor's current position.
template <typename T>
int TCircularDoublyLinkedList<T>::GetCursorIndex() const {
if (this->IsEmpty() || this->cursor == this->head) {
return -1;
}
int index = 0;
TNode<T>* current = this->head->GetNext();
// Traverse the list safely, knowing it will eventually loop back to the head.
while (current != this->head) {
if (current == this->cursor) {
return index;
}
current = current->GetNext();
index++;
}
return -1; // Should not be reached if cursor is valid.
}
// Appends a node to the end of the list (before the dummy head).
// Use the InternAppend from TDoublyLinkedList for basic logic, then adjust prev pointers.
template <typename T>
void TCircularDoublyLinkedList<T>::Append(const T& aData) {
// 1. Let the base class do ALL the standard work.
TDoublyLinkedList<T>::Append(aData);
// 2. Now fix the circular links.
if(!this->IsEmpty()) {
this->head->SetPrev(this->tail); // Dummy head's prev points to new tail
this->tail->SetNext(this->head); // New tail's next points to dummy head
}
}
// Prepends a node to the beginning of the list (after the dummy head).
// Use the Prepend from TDoublyLinkedList, this will handle all the logic.
template <typename T>
void TCircularDoublyLinkedList<T>::Prepend(const T& aData) {
TDoublyLinkedList<T>::Prepend(aData);
// No need to adjust circular links, as TDoublyLinkedList::Prepend already does it.
}
template <typename T>
void TCircularDoublyLinkedList<T>::InternalRemoveNode(TNode<T>* aNodeToDelete) {
if (aNodeToDelete == nullptr || aNodeToDelete == this->head) return; // Invalid input
// 1. Update cursor if it points to the node being deleted
if (this->cursor == aNodeToDelete) {
this->cursor = aNodeToDelete->GetNext();
}
//2. Update tail if the last node is being removed
if (aNodeToDelete == this->tail) {
this->tail = aNodeToDelete->GetPrev();
}
// 3. Relink neighbors (this maintains the circle automatically)
TNode<T>* prevNode = aNodeToDelete->GetPrev();
TNode<T>* nextNode = aNodeToDelete->GetNext();
prevNode->SetNext(nextNode);
nextNode->SetPrev(prevNode);
// 4. Delete data if owned, then delete the node
if (this->isDataOwner) {
delete aNodeToDelete->GetData();
}
delete aNodeToDelete;
this->size--;
// 5. If the list is now empty, ensure tail and cursor point to the head.
if (this->IsEmpty()) {
this->tail = this->head;
this->cursor = this->head;
}
}
// Removes the first node with the given value.
template <typename T>
void TCircularDoublyLinkedList<T>::Remove(const T& aData) {
if (this->IsEmpty()) return;
// Start searching from the first actual node
TNode<T>* current = this->head->GetNext();
// Traverse the entire circle once
for (int i = 0; i < this->size; ++i) {
if (current->GetData() == aData) {
TNode<T>* prevNode = current->GetPrev();
TNode<T>* nextNode = current->GetNext();
// Update tail if the last node is being removed
if (current == this->tail) {
this->tail = prevNode;
}
// Relink neighbors (this maintains the circle automatically)
prevNode->SetNext(nextNode);
nextNode->SetPrev(prevNode);
if (this->isDataOwner) {
delete current->GetData();
}
delete current;
this->size--;
return;
}
current = current->GetNext();
}
}
// Removes the last element from the list.
template <typename T>
T TCircularDoublyLinkedList<T>::RemoveLast() {
if (this->IsEmpty()) {
return nullptr;
}
TNode<T>* nodeToRemove = this->tail;
T dataToReturn = nodeToRemove->GetData();
TNode<T>* newTail = nodeToRemove->GetPrev();
// Relink the new tail to the head to maintain the circle
newTail->SetNext(this->head);
this->head->SetPrev(newTail);
this->tail = newTail; // Update tail pointer
delete nodeToRemove;
this->size--;
if (this->IsEmpty()) {
this->tail = this->head; // Reset tail if list is now empty
}
return dataToReturn;
}
// Removes all occurrences of a given value using a safe, bounded loop.
template <typename T>
void TCircularDoublyLinkedList<T>::RemoveAll(const T& aData) {
if (this->IsEmpty()) return;
TNode<T>* current = this->head->GetNext();
int initialSize = this->size; // Loop based on the original size
for (int i = 0; i < initialSize; ++i) {
TNode<T>* nextNode = current->GetNext(); // Get next node before potential deletion
if (current->GetData() == aData) {
TNode<T>* prevNode = current->GetPrev();
if (current == this->tail) {
this->tail = prevNode;
}
prevNode->SetNext(nextNode);
nextNode->SetPrev(prevNode);
if (this->isDataOwner) {
delete current->GetData();
}
delete current;
this->size--;
}
current = nextNode;
}
if (this->IsEmpty()) {
this->tail = this->head;
}
}
// Reverses the entire list by swapping the next/prev pointers of every node.
template <typename T>
void TCircularDoublyLinkedList<T>::Reverse() {
if (this->size <= 1) return; // Nothing to reverse
// 1. Keep track of original head and tail nodes.
TNode<T>* newFirstNode = this->tail;
// 2. Iterate through all nodes, swapping next and prev pointers.
TNode<T>* current = this->head;
// We must loop size + 1 times to include the dummy head in the pointer swap.
for (int i = 0; i < this->size + 1; ++i) {
current->SwapNextPrev();
// The *new* prev pointer is the *original* next pointer, so this moves us forward.
current = current->GetPrev();
}
// 3. Correct the head and tail pointers.
this->tail = this->head->GetNext(); // The old head is the new tail.
this->head->SetNext(newFirstNode); // The dummy head now points to the old tail.
newFirstNode->SetPrev(this->head); // New first node's prev points to dummy head.
}
// Reverses a portion of the list, ensuring circular links are maintained.
template <typename T>
void TCircularDoublyLinkedList<T>::ReverseSublist(int start, int end) {
if (start < 0 || end >= this->size || start >= end) {
return;
}
// --- 1. Find boundary nodes ---
TNode<T>* startNode = this->head->GetNext();
for (int i = 0; i < start; ++i) {
startNode = startNode->GetNext();
}
TNode<T>* endNode = startNode;
for (int i = start; i < end; ++i) {
endNode = endNode->GetNext();
}
TNode<T>* startPrev = startNode->GetPrev();
TNode<T>* endNext = endNode->GetNext();
// --- 2. Reverse pointers for all nodes within the sublist ---
TNode<T>* current = startNode;
for (int i = 0; i <= (end - start); ++i) {
TNode<T>* temp = current->GetNext();
current->SetNext(current->GetPrev());
current->SetPrev(temp);
current = temp;
}
// --- 3. Re-stitch the sublist (no nullptr checks needed) ---
startPrev->SetNext(endNode);
endNode->SetPrev(startPrev);
startNode->SetNext(endNext);
endNext->SetPrev(startNode);
// Update tail pointer if it was part of the reversed segment
if (this->head->GetPrev() == endNode) { // Original startNode is now at the end
this->tail = startNode;
}
}
// Merges another sorted list, creating a single, sorted circular list.
template <typename T>
void TCircularDoublyLinkedList<T>::Merge(TSingleLinkedList<T>& otherList) {
// We can call the parent implementation to do the heavy lifting of merging.
TDoublyLinkedList<T>::Merge(otherList);
// The parent merge results in a null-terminated list. We just need to fix the ends.
if (!this->IsEmpty()) {
TNode<T>* firstNode = this->head->GetNext();
this->tail->SetNext(firstNode);
firstNode->SetPrev(this->tail);
}
}
// Applies a function to each node using a safe loop for a circular list.
template <typename T>
void TCircularDoublyLinkedList<T>::ForEach(FVisitNode<T> aVisitNode) const {
if (aVisitNode == nullptr || this->IsEmpty()) {
return;
}
TNode<T>* current = this->head->GetNext();
int index = 0;
while (current != this->head) {
aVisitNode(current->GetData(), index);
current = current->GetNext();
index++;
}
}
// Checks if a value is in the list using a safe, non-infinite loop.
template <typename T>
bool TCircularDoublyLinkedList<T>::Contains(const T& aData) const {
if (this->IsEmpty()) {
return false;
}
TNode<T>* current = this->head->GetNext();
while (current != this->head) {
if (current->GetData() == aData) {
return true;
}
current = current->GetNext();
}
return false;
}
// Searches for a value using a safe, non-infinite loop.
template <typename T>
T TCircularDoublyLinkedList<T>::Search(const T& aData, FCheckNode<T> aCheckNode) const {
if (this->IsEmpty()) {
return nullptr;
}
TNode<T>* current = this->head->GetNext();
while (current != this->head) {
if (aCheckNode != nullptr) {
if (aCheckNode(current->GetData(), aData)) {
return current->GetData();
}
}
else {
if (current->GetData() == aData) {
return current->GetData();
}
}
current = current->GetNext();
}
return nullptr;
}
// Finds the middle node by traversing to the size/2 index.
template <typename T>
TNode<T>* TCircularDoublyLinkedList<T>::GetMiddle() const {
if (this->IsEmpty()) return nullptr;
// The safest way for a list where we know the size.
int middleIndex = this->size / 2;
TNode<T>* current = this->head->GetNext();
for (int i = 0; i < middleIndex; ++i) {
current = current->GetNext();
}
return current;
}
#endif // TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP