init lab 3
This commit is contained in:
parent
7b40a4243d
commit
2314cf70aa
21
lab_3/sequences-int/Makefile
Normal file
21
lab_3/sequences-int/Makefile
Normal 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
|
164
lab_3/sequences-int/doubly-linked-list.cpp
Normal file
164
lab_3/sequences-int/doubly-linked-list.cpp
Normal 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 **/
|
||||||
|
}
|
104
lab_3/sequences-int/doubly-linked-list.h
Normal file
104
lab_3/sequences-int/doubly-linked-list.h
Normal 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
|
95
lab_3/sequences-int/dynamic-array.cpp
Normal file
95
lab_3/sequences-int/dynamic-array.cpp
Normal 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;
|
||||||
|
}
|
58
lab_3/sequences-int/dynamic-array.h
Normal file
58
lab_3/sequences-int/dynamic-array.h
Normal 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
|
||||||
|
};
|
||||||
|
}
|
101
lab_3/sequences-int/sequence-test.cpp
Normal file
101
lab_3/sequences-int/sequence-test.cpp
Normal 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";
|
||||||
|
}
|
42
lab_3/sequences-int/sequence.h
Normal file
42
lab_3/sequences-int/sequence.h
Normal 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
|
140
lab_3/sequences-int/singly-linked-list.cpp
Normal file
140
lab_3/sequences-int/singly-linked-list.cpp
Normal 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;
|
||||||
|
}
|
89
lab_3/sequences-int/singly-linked-list.h
Normal file
89
lab_3/sequences-int/singly-linked-list.h
Normal 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
|
Loading…
Reference in New Issue
Block a user