Recently I wanted to use the gtest
library to unit test my program but for some reason, it's incredibly hard to make it work and configure (at least for me it was), so I decided to make my own, following the style Google uses.
I set a goal for this summer and it is to make a Linked List library similar to std::list
. I think I am getting there slowly and making some good progress on the way. So far I've implemented about 20 methods and there is more to come. I decided to use the TEST
class to test it and it worked well I think.
- Currently, I am using bubble sort to sort my list which runs in
O(n^2)
, so an implementation of anO(nlogn)
is on the todo list - I am also using recursion in some places which will probably gobble up all my space for larger inputs. I am working on an iterative solution and comparing results to see if it's even worth it... (recursive code looks cleaner IMHO)
- I am also missing iterators to traverse the list but when I thought about implementing them I saw they exceed my knowledge of C++ (for now)
- No restrictions, this is a personal project
I would like to know where I can improve my code and make it faster for larger input. I would also like to know if there are some major design flaws I should be aware of before proceeding, some code is repetitive but that's because I don't fully understand how std::function()
works.
Is there a better way to write Makefiles
? I have been CmdC + CmdV the same exact one since I started working with classes in C++. (not a joke)
I built the project in XCode
but I also wrote a MakeFile
so it should compile. For me, it compiles with [compiler]: Apple LLVM 14.0.3 (clang-1403.0.22.14.1)
P.S.:
I know
std::list
is a doubly linked list...mine is not and I am planning on making one later on, that's why I used a namespace to distinguish them. Please let me know if this is not the right approach and if some other OOP method would be a better fit. I eventually want to avoid namespace pollution since most of the methods would have the same name. Perhaps making all of the methods in one class static? (doesn’t sound productive but could it work?)There are some
#include "___.cpp"
which might come as cringe to some more experienced programmers but I had some linking issues and kept getting anassembly
error, including.cpp
instead of.h
fixed my issue. I am aware that grabbing and pasting the whole code base is considered unpractical.
Edit: I see my question hasn't been answered for a while, is it perhaps not specific enough? I set a bounty hoping it would help.
main.cpp
//
// main.cpp
// linked_list_xcode
//
// Created by Jakob Balkovec on 13/06/2023.
//
#include <iostream>
#include "single_l.h"
#include "test.h"
// {
// Un-comment test macro to run all tests
// }
// #define TEST
// {
// Un-comment example macro to run example
// }
// #define EXAMPLE
// {
// {
// Simple LL problem implemented in the example function
// }
// Problem Name: Merge Two Sorted Lists
// You are given two sorted linked lists, list1 and list2, where each list is sorted
// in non-decreasing order. Your task is to merge these two lists into a single
// sorted linked list and return the head of the merged list.
// Example:
// Input:
// list1: 1 -> 2 -> 3
// list2: 4 -> 5 -> 6
// Output: 1 -> 2 -> 3 -> 4 -> 5 -> 6
// Write a function mergeSortedLists that takes two sorted linked lists list1 and list2
// as parameters and returns the new merged list.
// You can assume the following:
// The linked lists are non-empty.
// The nodes of each list contain data of type T.
// The lists are already sorted in non-decreasing order.
// Note: You should not modify the original lists; instead,
// you should create a new list with the merged elements.
// }
/** {
* Displays information about the last compilation of the file.
* @return void
} **/
static inline void show_last_compiled() {
std::cout << "\n[file]: " << __FILE__ << '\n'
<< "[compiled on]: " << __DATE__ << " at " << __TIME__ << '\n'
<< "[compiler]: " << __VERSION__ << '\n'
<< "[timestamp]: " << __TIMESTAMP__ << '\n';
}
/**
* The main function of the C++ program. It runs either the tests or the example
* depending on the macros defined. If no macros are defined, it prints a message
* to the console.
*
* @param argc the number of command line arguments
* @param argv an array of the command line argument strings
*
* @return integer value EXIT_SUCCESS (0) upon successful execution
*
* @throws None
*/
int main(int argc, const char * argv[]) {
show_last_compiled();
#ifdef TEST
bool all_passed = run_tests();
if(all_passed)
std::cout << "\n\n[all tests passed]\n\n";
else
std::cout << "\n\n[some tests failed, check and see which]\n\n"
#endif
#ifdef EXAMPLE
run_example();
#endif
#ifndef TEST
#ifndef EXAMPLE
std::cout << "\n[Un-comment a macro to run]\n\n";
#endif
#endif
return EXIT_SUCCESS;
}
single_l.cpp
//
// single_l.cpp
// linked_list_xcode
//
// Created by Jakob Balkovec on 13/06/2023.
//
#include <iostream>
#include <unordered_set>
#include <stdexcept>
#include "single_l.h"
namespace single {
/**
* Creates a new single linked list
* @throws None
*/
template <class T>
single_l<T>::single_l() : head(nullptr) {}
/**
* Creates a new single linked list with the elements from the
* specified initializer list.
* @param initList the initializer list containing the elements to add
* @throws None
*/
template <class T>
single_l<T>::single_l(std::initializer_list<T> initList)
: head(nullptr) {
for (const T& value : initList) {
push_back(value);
}
}
/**
* Destructor for the single_l class.
* @param None
* @return None
* @throws None
*/
template <class T>
single_l<T>::~single_l() {
clear();
}
/**
* Constructs a new single linked list by copying all elements of another
* linked list.
* @param other The linked list to copy from.
* @throws None
*/
template <class T>
single_l<T>::single_l(const single_l& other) : head(nullptr) {
const single_l<T>::node* current = other.head;
while (current != nullptr) {
push_back(current->data_);
current = current->next_;
}
}
/**
* Constructs a single linked list by moving the contents of another single
* linked list, setting its head pointer to null afterwards.
* @param other the single linked list to move from
* @return none
* @throws none
*/
template <class T>
single_l<T>::single_l(single_l&& other) : head(nullptr) {
head = other.head;
other.head = nullptr;
}
/**
* @brief Assignment operator for the single_l class.
* @tparam T The type of elements in the list.
* @param other The list to be assigned.
* @return Reference to the modified list.
*/
template <class T>
single_l<T>& single_l<T>::operator=(const single_l& other) {
if (this != &other) {
clear();
const single_l<T>::node* current = other.head;
while (current != nullptr) {
push_back(current->data_);
current = current->next_;
}
}
return *this;
}
/**
* @brief Move assignment operator for the single_l class.
* @tparam T The type of elements in the list.
* @param other The list to be moved.
* @return Reference to the modified list.
*/
template <class T>
single_l<T>& single_l<T>::operator=(single_l&& other) {
if (this != &other) {
clear();
head = other.head;
other.head = nullptr;
}
return *this;
}
/**
* Adds a new node with the given data to the end of the linked list.
* @param data the data to be added to the linked list
* @return void
* @throws none
*/
template <class T>
void single_l<T>::push_back(const T& data) {
single_l<T>::node* new_node = new single_l<T>::node{ data, nullptr };
if (head == nullptr) {
head = new_node;
}
else {
single_l<T>::node* current = head;
while (current->next_ != nullptr) {
current = current->next_;
}
current->next_ = new_node;
}
return;
}
/**
* Adds a new node to the front of the linked list.
* @param data The data to be stored in the new node.
* @return void.
* @throws None.
*/
template <class T>
void single_l<T>::push_front(const T& data) {
single_l<T>::node* new_node = new single_l<T>::node{ data, head };
head = new_node;
return;
}
/**
* Removes the last element of the single linked list. Throws a runtime error
* if the list is empty.
* @throws std::runtime_error [pop_back()] [list is empty]
*/
template <class T>
void single_l<T>::pop_back() {
try {
if (head == nullptr) {
throw std::runtime_error("[pop_back()] [list is empty]\n");
}
else if (head->next_ == nullptr) {
delete head;
head = nullptr;
}
else {
single_l<T>::node* current = head;
while (current->next_->next_ != nullptr) {
current = current->next_;
}
delete current->next_;
current->next_ = nullptr;
}
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
return;
}
/**
* Pops the first element from the list.
* @param None
* @return None
* @throws std::runtime_error if the list is empty
*/
template <class T>
void single_l<T>::pop_front() {
try {
if (head == nullptr) {
throw std::runtime_error("[pop_front()] [list is empty]\n");
}
single_l<T>::node* temp = head;
head = head->next_;
delete temp;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
return;
}
/**
* Recursively prints the linked list in reverse order, starting from the given node.
* @param current pointer to the current node being processed
* @return void
* @throws none
*/
template <class T>
void single_l<T>::print_reverse_helper(single_l<T>::node* current) {
if (current == nullptr) {
return;
}
print_reverse_helper(current->next_);
std::cout << current->data_ << " ";
}
/**
* Recursively prints the data of each node in a single linked list, starting from
* the given node.
* @param current the node to start printing from
* @return void
*/
template <class T>
void single_l<T>::print_helper(single_l<T>::node* current) {
if (current == nullptr) {
return;
}
std::cout << current->data_ << " ";
print_helper(current->next_);
}
/**
* Prints the contents of the list in reverse order.
* @throws std::runtime_error if the list is empty
*/
template <class T>
void single_l<T>::print_reverse() {
try {
if (head == nullptr) {
throw std::runtime_error("[print_reverse()] [list is empty]");
}
std::cout << "{ ";
print_reverse_helper(head);
std::cout << "}";
std::cout << std::endl;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
}
/**
* Prints the elements of the single linked list.
* @throws std::runtime_error if the list is empty.
*/
template <class T>
void single_l<T>::print() {
try {
if (head == nullptr) {
throw std::runtime_error("[print()] [list is empty]");
}
std::cout << "{ ";
print_helper(head);
std::cout << "}";
std::cout << std::endl;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
}
/**
* Clears the linked list by deleting all nodes. Starts from the head of the
* linked list and deletes each node until the list is empty.
* @param None
* @return None
* @throws None
*/
template <class T>
void single_l<T>::clear() {
while (head != nullptr) {
single_l<T>::node* temp = head;
head = head->next_;
delete temp;
}
}
/**
* Swaps the head of this list with another list's head.
* @param other The other list to swap heads with.
* @throws None
*/
template <class T>
void single_l<T>::swap(single_l& other) {
std::swap(head, other.head);
}
/**
* Determines whether the single_l<T> object is empty.
* @return true if the object is empty, false otherwise
*/
template <class T>
bool single_l<T>::empty() {
return (head == nullptr) ? true : false;
}
/**
* Returns the number of nodes in the linked list.
* @param None
* @return size_t The number of nodes in the linked list.
* @throws None
*/
template <class T>
size_t single_l<T>::size() {
size_t count = 0;
single_l<T>::node* current = head;
while (current != nullptr) {
count++;
current = current->next_;
}
return count;
}
/**
* @brief Returns a reference to the first element in the list.
* @tparam T The type of elements in the list.
* @return Reference to the first element.
* @throw std::runtime_error If the list is empty.
*/
template <class T>
T& single_l<T>::front() {
try {
if (head == nullptr) {
throw std::runtime_error("[front()] [list is empty]");
}
return head->data_;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Returns a reference to the last element in the list.
* @tparam T The type of elements in the list.
* @return Reference to the last element.
* @throw std::runtime_error If the list is empty.
*/
template <class T>
T& single_l<T>::back() {
try {
if (head == nullptr) {
throw std::runtime_error("[back()] [list is empty]");
}
single_l<T>::node* current = head;
while (current->next_ != nullptr) {
current = current->next_;
}
return current->data_;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Returns a const reference to the first element in the list.
* @tparam T The type of elements in the list.
* @return Const reference to the first element.
* @throw std::runtime_error If the list is empty.
*/
template <class T>
const T& single_l<T>::front() const {
try {
if (head == nullptr) {
throw std::runtime_error("[front() cosnt] [list is empty]");
}
return head->data_;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Returns a const reference to the last element in the list.
* @tparam T The type of elements in the list.
* @return Const reference to the last element.
* @throw std::runtime_error If the list is empty.
*/
template <class T>
const T& single_l<T>::back() const {
try {
if (head == nullptr) {
throw std::runtime_error("[back() cosnt] [list is empty]");
}
single_l<T>::node* current = head;
while (current->next_ != nullptr) {
current = current->next_;
}
return current->data_;
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Accesses the element at the specified index in the list.
* @tparam T The type of elements in the list.
* @param index The index of the element to access.
* @return Reference to the element at the specified index.
* @throw std::runtime_error If the list is empty.
* @throw std::out_of_range If the index is out of range.
*/
template <class T>
T& single_l<T>::operator[](size_t index) {
try {
if (head == nullptr) {
throw std::runtime_error("[operator[]] [list is empty]");
}
single_l<T>::node* current = head;
size_t count = 0;
while (current != nullptr) {
if (count == index) {
return current->data_;
}
current = current->next_;
count++;
}
throw std::out_of_range("[operator[]] [index out of range]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Accesses the element at the specified index in the list (const version).
* @tparam T The type of elements in the list.
* @param index The index of the element to access.
* @return Const reference to the element at the specified index.
* @throw std::runtime_error If the list is empty.
* @throw std::out_of_range If the index is out of range.
*/
template <class T>
const T& single_l<T>::operator[](size_t index) const {
try {
if (head == nullptr) {
throw std::runtime_error("[operator[] const] [list is empty]");
}
single_l<T>::node* current = head;
size_t count = 0;
while (current != nullptr) {
if (count == index) {
return current->data_;
}
current = current->next_;
count++;
}
throw std::out_of_range("[operator[] const] [index out of range]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
}
/**
* @brief Accesses the element at the specified index in the list.
* @tparam T The type of elements in the list.
* @param index The index of the element to access.
* @return Reference to the element at the specified index.
* @throw std::runtime_error If the list is empty.
* @throw std::out_of_range If the index is out of range.
*/
template <class T>
T& single_l<T>::at(size_t index) {
try {
if (head == nullptr) {
throw std::runtime_error("[at()] [list is empty]");
}
single_l<T>::node* current = head;
size_t count = 0;
while (current != nullptr) {
if (count == index) {
return current->data_;
}
current = current->next_;
count++;
}
throw std::out_of_range("[at()] [index out of range]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
}
static T default_value;
return default_value;
}
/**
* @brief Deletes the element at the specified index in the list.
* @tparam T The type of elements in the list.
* @param index The index of the element to delete.
* @throw std::runtime_error If the list is empty.
* @throw std::out_of_range If the index is out of range.
*/
template <class T>
void single_l<T>::delete_at(size_t index) {
try {
if (head == nullptr) {
throw std::runtime_error("[delete_at()] [list is empty]");
}
if (index == 0) {
pop_front();
return;
}
single_l<T>::node* current = head;
single_l<T>::node* previous = nullptr;
size_t count = 0;
while (current != nullptr) {
if (count == index) {
if (previous != nullptr) {
previous->next_ = current->next_;
}
else {
head = current->next_;
}
delete current;
return;
}
previous = current;
current = current->next_;
count++;
}
throw std::out_of_range("[delete_at()] [index out of range]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
throw; // rethrow the exception to propagate it
}
}
/**
* @brief Deletes the first occurrence of the specified value in the list.
* @tparam T The type of elements in the list.
* @param data The value to delete.
* @throw std::runtime_error If the list is empty or the value is not found.
*/
template <class T>
void single_l<T>::delete_value(const T& data) {
try {
if (head == nullptr) {
throw std::runtime_error("[delete_value()] [list is empty]");
}
single_l<T>::node* current = head;
single_l<T>::node* previous = nullptr;
while (current != nullptr) {
if (current->data_ == data) {
if (previous == nullptr) {
head = current->next_;
}
else {
previous->next_ = current->next_;
}
delete current;
return;
}
previous = current;
current = current->next_;
}
throw std::runtime_error("[delete_value()] [value not found]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
throw; // rethrow the exception to propagate it
}
}
/**
* Searches for the given data element in the single linked list and returns
* the index of the first occurrence. If the element is not found, it returns
* -1.
* @param data The element to search in the linked list.
*
* @return The index of the first occurrence of the element in the linked list.
* If the element is not found, it returns -1.
* @throws None
*/
template <class T>
size_t single_l<T>::search(const T& data) {
size_t index = 0;
single_l<T>::node* current = head;
while (current != nullptr) {
if (current->data_ == data) {
return index;
}
current = current->next_;
index++;
}
return static_cast<size_t>(-1); // return -1 if value not found for clarity
}
/**
* Inserts a new node with given data at the specified position in the linked list.
* @param position_data the data to be inserted at the specified position
* @param position the position at which the new node is to be inserted
* @throws std::runtime_error if the list is empty
* @throws std::out_of_range if the position is out of range
*/
template <class T>
void single_l<T>::at_position(const T& position_data, size_t position) {
try {
if (head == nullptr) {
throw std::runtime_error("[at_position()] [list is empty]");
}
single_l<T>::node* current = head;
single_l<T>::node* previous = nullptr;
size_t count = 0;
while (current != nullptr) {
if (count == position) {
single_l<T>::node* new_node = new single_l<T>::node;
new_node->data_ = position_data;
new_node->next_ = current;
if (previous == nullptr) {
head = new_node;
}
else {
previous->next_ = new_node;
}
return;
}
previous = current;
current = current->next_;
count++;
}
throw std::out_of_range("[at_position()] [position out of range]");
}
catch (const std::exception& e) {
std::cerr << "{error}: " << e.what() << std::endl;
throw; // rethrow the exception to propagate it
}
}
/**
* Reverses the order of the nodes in a singly linked list.
* @param none
* @return void
* @throws none
*/
template <class T>
void single_l<T>::reverse() {
if (head == nullptr || head->next_ == nullptr) {
return;
}
single_l<T>::node* current = head;
single_l<T>::node* previous = nullptr;
single_l<T>::node* next = nullptr;
while (current != nullptr) {
next = current->next_;
current->next_ = previous;
previous = current;
current = next;
}
head = previous;
}
/**
* Sorts the linked list in ascending order using bubble sort algorithm.
* @param None.
* @return None.
* @throws None.
*/
template <class T>
void single::single_l<T>::sort() {
if (head == nullptr || head->next_ == nullptr) {
return;
}
bool swapped;
single_l<T>::node* current;
single_l<T>::node* last = nullptr;
do {
swapped = false;
current = head;
while (current->next_ != last) {
if (current->data_ > current->next_->data_) {
std::swap(current->data_, current->next_->data_);
swapped = true;
}
current = current->next_;
}
last = current;
} while (swapped);
}
/**
* Removes duplicates from a singly linked list.
* @param None
* @return None
* @throws None
*/
template <class T>
void single_l<T>::remove_duplicates() {
if (head == nullptr || head->next_ == nullptr) {
return;
}
std::unordered_set<T> unique_value;
single_l<T>::node* current = head;
single_l<T>::node* previous = nullptr;
while (current != nullptr) {
if (unique_value.count(current->data_) > 0) {
previous->next_ = current->next_;
delete current;
current = previous->next_;
}
else {
unique_value.insert(current->data_);
previous = current;
current = current->next_;
}
}
}
/**
* Resizes the list to contain n elements.
* @param n the new size of the list
* @return void
* @throws None
*/
template <class T>
void single::single_l<T>::resize(size_t n) {
if (n == 0) {
clear();
}
else if (n > size()) {
size_t additional_nodes = n - size();
if (head == nullptr) {
head = new node;
head->next_ = nullptr;
additional_nodes--;
}
node* current = head;
while (current->next_ != nullptr) {
current = current->next_;
}
for (size_t i = 0; i < additional_nodes; i++) {
node* new_node = new node;
new_node->next_ = nullptr;
current->next_ = new_node;
current = new_node;
}
}
else if (n < size()) {
node* current = head;
node* previous = nullptr;
for (size_t i = 0; i < n; i++) {
previous = current;
current = current->next_;
}
while (current != nullptr) {
node* next_node = current->next_;
delete current;
current = next_node;
}
previous->next_ = nullptr;
}
}
} //namespace single
single_l.h
//
// single_l.h
// linked_list_xcode
//
// Created by Jakob Balkovec on 13/06/2023.
//
#ifndef single_l_h
#define single_l_h
#include <iostream>
namespace single {
template <class T>
class single_l final {
private:
struct node {
T data_;
node* next_;
};
struct node* head;
public:
single_l();
single_l(std::initializer_list<T>_init_);
~single_l();
single_l(const single_l&);
single_l(single_l&&);
single_l& operator=(const single_l&);
single_l& operator=(single_l&&);
void push_back(const T&);
void push_front(const T&);
void pop_back();
void pop_front();
void print();
void print_reverse();
void print_helper(single_l<T>::node* current);
void print_reverse_helper(single_l<T>::node* current);
void clear();
void swap(single_l&);
bool empty();
size_t size();
T& front();
T& back();
const T& front() const;
const T& back() const;
T& operator[](size_t);
const T& operator[](size_t) const;
T& at(size_t index); //inserts at index
void delete_at(size_t index);
void delete_value(const T &data);
size_t search(const T &data);
void at_position(const T& data, size_t position); //inserts at specific spot
void reverse();
void sort();
void remove_duplicates();
void resize(size_t n);
};
} //namespace single
#endif /* single_l_h */
test_file.h
//
// test_file.h
// linked_list_xcode
//
// Created by Jakob Balkovec on 13/06/2023.
//
#ifndef test_file_h
#define test_file_h
#include <iostream>
#include <string>
template <class T>
class TEST {
private:
/**
* Prints a test passing message with an optional function name.
* @param func_name an optional name of the function being tested
* @return void
* @throws none
*/
static void PRINT_PASS(const std::string& func_name = "") {
std::cout << "[TEST " << func_name << " PASSED]" << std::endl;
}
/**
* Prints a failure message for a test, including an optional message and function name.
* @param message the message to include in the failure message (optional)
* @param func_name the name of the function being tested (optional)
* @return void
* @throws none
*/
static void PRINT_FAIL(const std::string& message, const std::string& func_name = "") {
std::cout << "[TEST " << func_name << " FAILED]";
if (!message.empty()) {
std::cout << " " << message;
}
std::cout << std::endl;
}
public:
/**
* A function that checks a boolean condition, prints a pass message if the condition is true,
* otherwise prints a fail message with an optional custom message and function name.
* @param condition the boolean condition to check
* @param message an optional custom message to print in the fail message
* @param func_name an optional function name to print in the fail message
* @throws no exceptions thrown
*/
static void ASSERT(bool condition, const std::string& message = "", const std::string& func_name = "") {
if (condition) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
}
}
/**
* Compares the expected and actual values and prints a pass or fail
* message with an optional custom message along with the expected and actual values.
* @tparam T the data type of the expected and actual values
* @param expected the expected value
* @param actual the actual value
* @param message an optional message to print on failure
* @param func_name an optional function name to print in the pass or fail message
* @return void
*/
static void ASSERT_EQUAL(const T& expected, const T& actual, const std::string& message = "", const std::string& func_name = "") {
if (expected == actual) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
std::cout << "\n[expected]: " << expected << " -> [actual]: " << actual << std::endl;
}
}
/**
* Compares the given actual and expected values and prints a pass message if they are not equal.
* Otherwise, prints a fail message and the actual and not expected values.
* @param notExpected the expected value
* @param actual the actual value to be compared with the expected value
* @param message an optional message to print if the comparison fails
* @param func_name an optional name of the function for debugging purposes
* @throws none
*/
static void ASSERT_NOT_EQUAL(const T& notExpected, const T& actual, const std::string& message = "", const std::string& func_name = "") {
if (notExpected != actual) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
std::cout << "\n[not expected]: " << notExpected << " -> [actual]: " << actual << std::endl;
}
}
/**
* A function that asserts whether a condition is true. If the condition is true,
* prints a pass message; otherwise, prints a fail message.
* @param condition a boolean representing the condition to be tested
* @param message optional message to be printed in the event of a failure
* @param func_name optional name of the function being tested
* @throws none
*/
static void ASSERT_TRUE(bool condition, const std::string& message = "", const std::string& func_name = "") {
if (condition) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
}
}
/**
* Checks if a given condition is false and prints either a pass message or a fail
* message with a given function name and message.
* @param condition boolean expression to check
* @param message optional message to print if the condition is true
* @param func_name optional function name to print with the fail message
* @throws none
*/
static void ASSERT_FALSE(bool condition, const std::string& message = "", const std::string& func_name = "") {
if (!condition) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
}
}
/**
* Runs a statement and catches any exceptions of type ExceptionType.
* If an exception of type ExceptionType is caught, the function will print a pass message.
* If another type of exception is caught, the function will print a fail message
* and the type of exception thrown.
* If no exception is thrown, the function will print a fail message.
* @param statement the statement to run
* @param message an optional message to print if the statement fails
* @param func_name an optional name of the function being tested
*
* @throws ExceptionType if the statement throws an exception of this type
*/
template <typename ExceptionType>
static void ASSERT_THROW(const std::function<void()>& statement, const std::string& message = "", const std::string& func_name = "") {
try {
statement();
PRINT_FAIL(message, func_name);
} catch (const ExceptionType&) {
PRINT_PASS(func_name);
} catch (...) {
PRINT_FAIL(message, func_name);
std::cout << "[expected exception type]: " << typeid(ExceptionType).name() << std::endl;
}
}
/**
* Method that takes in a std::function object,
* a string message, and a string function name. It attempts to execute the provided statement,
* and if an exception of type ExceptionType is thrown, it prints a failed message and an
* optional error message and function name. If any other type of exception is thrown, it
* prints a failed message and an optional error message and function name.
* @param statement The std::function object that is to be executed.
* @param message Optional string error message to print alongside the failed message.
* @param func_name Optional string function name to print alongside the failed message.
* @throws ExceptionType If this type of exception is thrown by the statement.
*/
template <typename ExceptionType>
static void ASSERT_NO_THROW(const std::function<void()>& statement, const std::string& message = "", const std::string& func_name = "") {
try {
statement();
PRINT_PASS(func_name);
} catch (const ExceptionType&) {
PRINT_FAIL(message, func_name);
std::cout << "[unexpected exception type]: " << typeid(ExceptionType).name() << std::endl;
} catch (...) {
PRINT_FAIL(message, func_name);
}
}
/**
* Compares the given value with a given threshold and prints a pass
* or fail message based on the comparison.
* @param value the value to be compared
* @param threshold the threshold value to compare against
* @param message an optional message to be printed in case of a fail
* @param func_name the name of the calling function
* @throws None
*/
static void GREATER(const T& value, const T& threshold, const std::string& message = "", const std::string& func_name = "") {
if (value > threshold) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
std::cout << "[value]: " << value << " -> [threshold]: " << threshold << std::endl;
}
}
/**
* Compares the given value with the given threshold and prints a message based on the result.
* @param value the value to compare with the threshold
* @param threshold the threshold to compare the value with
* @param message an optional message to print if the comparison fails
* @param func_name an optional function name to print in the pass/fail message
* @throws N/A
*/
static void SMALLER(const T& value, const T& threshold, const std::string& message = "", const std::string& func_name = "") {
if (value < threshold) {
PRINT_PASS(func_name);
} else {
PRINT_FAIL(message, func_name);
std::cout << "[value]: " << value << " -> [threshold]: " << threshold << std::endl;
}
}
};
#endif /* test_file_h */
test_file.cpp
//
// test_file.cpp
// linked_list_xcode
//
// Created by Jakob Balkovec on 13/06/2023.
//
#include "single_l.cpp"
#include "test_file.h"
#include "test.h"
#include <string>
#include <iostream>
// {
// Follow the arrange, act, assert principle
// }
/**
* Tests the default constructor of the 'single_l' template class using 'TEST' assertions.
* @param None
* @return Void
* @throws None
*/
void test_constructor() {
single::single_l<int> list1;
TEST<bool>::ASSERT_TRUE(list1.empty(), "[list should be empty]","DEF_CONSTRUCTOR {1}");
TEST<bool>::ASSERT_EQUAL(0, list1.size(), "[list size should be 0]", "DEF_CONSTRUCTOR {2}");
}
/**
* This function tests the destructor of single_l<int> class. It creates a
* single_l<int> object, adds two elements to it and then calls the clear()
* method which in turn calls the destructor. It then uses the TEST macro to
* check that the list is empty and its size is 0.
* @param None
* @return None
* @throws None
*/
void test_destructor() {
single::single_l<int> list1;
list1.push_back(10);
list1.push_back(20);
list1.clear(); //destructor calls clear
TEST<bool>::ASSERT_TRUE(list1.empty(), "[list should be empty]", "DESTRUCTOR {1}");
TEST<bool>::ASSERT_TRUE(list1.size() == 0, "[size should be 0]", "DESTRUCTOR {2}");
}
/**
* Tests the copy constructor of the single_l class in the single namespace.
* @param None
* @return None
* @throws None
*/
void test_copy_constructor() {
single::single_l<int> list1;
list1.push_back(10);
list1.push_back(20);
single::single_l<int> list2(list1);
TEST<bool>::ASSERT_FALSE(list2.empty(), "[list should not be empty]", "CPY_CONSTRUCTOR {1}");
TEST<bool>::ASSERT_EQUAL(2, list2.size(), "[list size should be 2]", "CPY_CONSTRUCTOR {2}");
TEST<bool>::ASSERT_EQUAL(list1.front(), list2.front(), "[front elements should be equal]", "CPY_CONSTRUCTOR {3}");
TEST<bool>::ASSERT_EQUAL(list1.back(), list2.back(), "[back elements should be equal]", "CPY_CONSTRUCTOR {4}");
}
/**
* Tests the move constructor of a single linked list.
* @param None
* @return None
* @throws None
*/
void test_move_constructor() {
single::single_l<int> list1;
list1.push_back(10);
list1.push_back(20);
single::single_l<int> list2(std::move(list1));
TEST<bool>::ASSERT_TRUE(list1.empty(), "[source list should be empty]", "MV_CONSTRUCTOR {1}");
TEST<bool>::ASSERT_FALSE(list2.empty(), "[destination list should not be empty]", "MV_CONSTRUCTOR {2}");
TEST<bool>::ASSERT_EQUAL(2, list2.size(), "[list size should be 2]", "MV_CONSTRUCTOR {3}");
TEST<bool>::ASSERT_EQUAL(10, list2.front(), "[front element should be 10]", "MV_CONSTRUCTOR {4}");
TEST<bool>::ASSERT_EQUAL(20, list2.back(), "[back element should be 20]", "MV_CONSTRUCTOR {5}");
}
/**
* Tests the copy assignment operator of the single_l class.
* @param None
* @return None
* @throws None
*/
void test_copy_assignment_operator() {
single::single_l<int> list1;
list1.push_back(10);
list1.push_back(20);
single::single_l<int> list2;
list2.push_back(30);
list2 = list1;
TEST<bool>::ASSERT_FALSE(list2.empty(), "[list should not be empty]", "CPY_ASSGN_OP {1}");
TEST<bool>::ASSERT_EQUAL(2, list2.size(), "[list size should be 2]", "CPY_ASSGN_OP {2}");
TEST<bool>::ASSERT_EQUAL(list1.front(), list2.front(), "[front elements should be equal]", "CPY_ASSGN_OP {3}");
TEST<bool>::ASSERT_EQUAL(list1.back(), list2.back(), "[back elements should be equal]", "CPY_ASSGN_OP {4}");
}
/**
* Test the move assignment operator of a single linked list.
* @param None
* @return None
* @throws None
*/
void test_move_assignment_operator() {
single::single_l<int> list1;
list1.push_back(10);
list1.push_back(20);
single::single_l<int> list2;
list2.push_back(30);
list2 = std::move(list1);
TEST<bool>::ASSERT_TRUE(list1.empty(), "[source list should be empty]", "MV_ASSGN_OP {1}");
TEST<bool>::ASSERT_FALSE(list2.empty(), "[destination list should not be empty]", "MV_ASSGN_OP {2}");
TEST<bool>::ASSERT_EQUAL(2, list2.size(), "[list size should be 2]", "MV_ASSGN_OP {3}");
TEST<bool>::ASSERT_EQUAL(10, list2.front(), "[front element should be 10]", "MV_ASSGN_OP {4}");
TEST<bool>::ASSERT_EQUAL(20, list2.back(), "[back element should be 20]", "MV_ASSGN_OP {5}");
}
/**
* Tests the push_back() function of a single linked list implementation
* @param None
* @return None
* @throws None
*/
void test_push_back() {
single::single_l<int> list1;
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
list1.push_back(values[i]);
}
TEST<bool>::ASSERT_FALSE(list1.empty(), "[list not should be empty]", "PUSH_BACK {1}");
TEST<bool>::ASSERT_TRUE(list1[0] == 10, "[first element should be 10]", "PUSH_BACK {2}");
TEST<bool>::ASSERT_TRUE(list1[1] == 20, "[second element should be 20]", "PUSH_BACK {3}");
TEST<bool>::ASSERT_TRUE(list1[2] == 30, "[third element should be 30]", "PUSH_BACK {4}");
TEST<bool>::ASSERT_TRUE(list1[3] == 40, "[fourth element should be 40]", "PUSH_BACK {5}");
TEST<bool>::ASSERT_TRUE(list1[4] == 50, "[fifth element should be 50]", "PUSH_BACK {6}");
TEST<bool>::ASSERT_TRUE(list1.size() == 5, "[size should be 5]", "PUSH_BACK {7}");
}
/**
* Tests the push_front method of the single_l list implementation.
* @param None
* @return None
* @throws None
*/
void test_push_front() {
single::single_l<int> list1;
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
list1.push_front(values[i]);
}
TEST<bool>::ASSERT_FALSE(list1.empty(), "[list not should be empty]", "PUSH_FRONT {1}");
TEST<bool>::ASSERT_TRUE(list1[0] == 50, "[first element should be 50]", "PUSH_FRONT {2}");
TEST<bool>::ASSERT_TRUE(list1[1] == 40, "[second element should be 40]", "PUSH_FRONT {3}");
TEST<bool>::ASSERT_TRUE(list1[2] == 30, "[third element should be 30]", "PUSH_FRONT {4}");
TEST<bool>::ASSERT_TRUE(list1[3] == 20, "[fourth element should be 20]", "PUSH_FRONT {5}");
TEST<bool>::ASSERT_TRUE(list1[4] == 10, "[fifth element should be 10]", "PUSH_FRONT {6}");
TEST<bool>::ASSERT_TRUE(list1.size() == 5, "[size should be 5]", "PUSH_FRONT {7}");
}
/**
* Tests the pop_back method of a single linked list implementation by removing
* elements from the end of the list and asserting the expected values and sizes.
* @param none
* @return void
* @throws none
*/
void test_pop_back() {
single::single_l<int> list1;
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
list1.push_back(values[i]);
}
list1.pop_back();
TEST<bool>::ASSERT_EQUAL(40, list1[3], "[fourth element should be 40]", "POP_BACK {1}");
TEST<bool>::ASSERT_FALSE(list1.size() == 5, "[size shoudl be 4]", "POP_BACK {2}");
list1.pop_back();
TEST<bool>::ASSERT_EQUAL(30, list1[2], "[fourth element should be 40]", "POP_BACK {1}");
TEST<bool>::ASSERT_FALSE(list1.size() == 4, "[size shoudl be 4]", "POP_BACK {2}");
list1.pop_back();
list1.pop_back();
list1.pop_back();
TEST<bool>::ASSERT_TRUE(list1.empty(), "[list should be empty]", "POP_BACK {5}");
TEST<bool>::ASSERT_TRUE(list1.size() == 0, "[size should be 0]", "POP_BACK {6}");
}
/**
* Tests the pop_front() method of the single_l class by creating a list of
* integers and removing the first element multiple times while checking
* the state of the list after each removal.
* @param None
* @return None
* @throws None
*/
void test_pop_front() {
single::single_l<int> list1;
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
list1.push_back(values[i]);
}
list1.pop_front();
TEST<bool>::ASSERT_FALSE(list1[0] == 10, "[first element should not be 10]", "POP_FRONT {1}");
TEST<bool>::ASSERT_TRUE(list1[0] == 20, "[first element should be 20]", "POP_FRONT {2}");
list1.pop_front();
TEST<bool>::ASSERT_FALSE(list1[0] == 20, "[first element should not be 40]", "POP_FRONT {3}");
TEST<bool>::ASSERT_TRUE(list1[0] == 30, "[first element should be 30]", "POP_FRONT {4}");
list1.pop_front();
list1.pop_front();
list1.pop_front();
TEST<bool>::ASSERT_TRUE(list1.empty(), "[list should be empty]", "POP_FRONT {5}");
TEST<bool>::ASSERT_TRUE(list1.size() == 0, "[size should be 0]", "POP_FRONT {6}");
}
/**
* Tests the clear() method, the method clears the content of a single linked list and checks if the size is 0 and
* the list is empty.
* @param None
* @return None
* @throws None
*/
void test_clear() {
single::single_l<int> list1;
int values[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
list1.push_back(values[i]);
}
list1.clear();
TEST<bool>::ASSERT_EQUAL(0, list1.size(), "[size should be 0]", "CLEAR {1}");
TEST<bool>::ASSERT_EQUAL(true, list1.empty(), "[list should be empty]", "CLEAR {2}");
}
/**
* Executes a test swapping two linked lists of integers.
* @param None
* @return None
* @throws None
*/
void test_swap() {
single::single_l<int> list1;
int values1[] = {10, 20, 30, 40, 50};
for (int i = 0; i < sizeof(values1) / sizeof(values1[0]); i++) {
list1.push_back(values1[i]);
}
TEST<bool>::ASSERT_EQUAL(10, list1[0], "[list1 first element should be 10]", "SWAP {1}");
TEST<bool>::ASSERT_EQUAL(20, list1[1], "[lsit1 second element should be 20]", "SWAP {1}");
single::single_l<int> list2;
int values2[] = {60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values2) / sizeof(values2[0]); i++) {
list2.push_back(values2[i]);
}
TEST<bool>::ASSERT_EQUAL(60, list2[0], "[list2 first element should be 60]", "SWAP {3}");
TEST<bool>::ASSERT_EQUAL(70, list2[1], "[list2 second element should be 70]", "SWAP {4}");
//swaps two lists
list1.swap(list2);
TEST<bool>::ASSERT_EQUAL(60, list1[0], "[list1 first element should be 60]", "SWAP {5}");
TEST<bool>::ASSERT_EQUAL(70, list1[1], "[list1 second element should be 70]", "SWAP {6}");
TEST<bool>::ASSERT_EQUAL(10, list2[0], "[list2 first element should be 10]", "SWAP {7}");
TEST<bool>::ASSERT_EQUAL(20, list2[1], "[list2 second element should be 20]", "SWAP {8}");
TEST<bool>::ASSERT(list1.size() == list2.size(), "[list1 and list2 size should be equal]", "SWAP {9}");
}
/**
* Tests if the given single linked list is empty.
* @param None
* @return void
* @throws None
*/
void test_empty() {
single::single_l<int> list1;
int values1[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values1) / sizeof(values1[0]); i++) {
list1.push_back(values1[i]);
}
TEST<bool>::ASSERT_FALSE(list1.empty(), "[list should not be empty]", "EMPTY {1}");
list1.clear();
TEST<bool>::ASSERT_TRUE(list1.empty(), "[list should be empty]", "EMPTY {2}");
}
void test_size() {
single::single_l<int> list1;
int values1[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values1) / sizeof(values1[0]); i++) {
list1.push_back(values1[i]);
}
TEST<bool>::ASSERT_EQUAL(static_cast<size_t>(10), static_cast<int>(const_cast<single::single_l<int>&>(list1).size()), "[size should be 10]", "SIZE {1}");
list1.pop_back();
TEST<bool>::ASSERT_EQUAL(static_cast<size_t>(9), static_cast<int>(const_cast<single::single_l<int>&>(list1).size()), "[size should be 9]", "SIZE {2}");
}
/**
* Tests whether the front element of a single linked list is correct after
* several push and pop operations.
* @return void
* @throws None
*/
void test_front() {
single::single_l<int> list1;
int values1[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values1) / sizeof(values1[0]); i++) {
list1.push_back(values1[i]);
}
TEST<bool>::ASSERT_EQUAL(10, list1.front(), "[front should be 10]", "FRONT {1}");
list1.pop_back();
TEST<bool>::ASSERT_EQUAL(10, list1.front(), "[front should be 10]", "FRONT {2}");
list1.pop_front();
TEST<bool>::ASSERT_EQUAL(20, list1.front(), "[front should be 20]", "FRONT {3}");
for(auto i = 0; i < 7; i++) {
list1.pop_front();
}
TEST<bool>::ASSERT_EQUAL(90, list1.front(), "[front should be 90]", "FRONT {4}");
}
/**
* Tests the back() method of the single_l<int> class.
* @param None
* @return None
* @throws None
*/
void test_back() {
single::single_l<int> list1;
int values1[] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
for (int i = 0; i < sizeof(values1) / sizeof(values1[0]); i++) {
list1.push_back(values1[i]);
}
TEST<bool>::ASSERT_EQUAL(100, list1.back(), "[back should be 100]", "BACK {1}");
list1.pop_front();
TEST<bool>::ASSERT_EQUAL(100, list1.back(), "[back should be 100]", "BACK {2}");
list1.pop_back();
TEST<bool>::ASSERT_EQUAL(90, list1.back(), "[back should be 90]", "BACK {3}");
for(auto i = 0; i < 7; i++) {
list1.pop_back();
}
TEST<bool>::ASSERT_EQUAL(20, list1.back(), "[back should be 20]", "BACK {4}");
}
/**
* Tests the front() function of the single_l class with const objects.
* @param None
* @return None
* @throws None
*/
void test_front_const() {
const single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(10, list1.front(), "[front should be 10]", "FRONT CONST {1}");
const single::single_l<int> list2{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TEST<bool>::ASSERT_EQUAL(1, list2.front(), "[front should be 1]", "FRONT CONST {2}");
}
/**
* Tests the back() method of the single::single_l class when the list is const.
* @param None
* @return None
* @throws None
*/
void test_back_const() {
const single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(100, list1.back(), "[front should be 100]", "BACK CONST {1}");
const single::single_l<int> list2{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TEST<bool>::ASSERT_EQUAL(10, list2.back(), "[front should be 10]", "BACK CONST {2}");
}
/**
* Test the subscript operator of a single linked list.
* @param None
* @return None
* @throws None
*/
void test_sub_operator() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(100, list1[list1.size()-1], "[should return 100]", "SUB_OP {1}");
single::single_l<int> list2{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TEST<bool>::ASSERT_EQUAL(4, list2[3], "[should return 4]", "SUP_OP {2}");
}
/**
* Tests the at() function of a single_l<int> list. It asserts that the
* values returned by at() match the expected values at all positions.
* @param None
* @return None
* @throws None
*/
void test_at() {
single::single_l<int> list1{1, 2, 3, 4, 5};
TEST<bool>::ASSERT_EQUAL(1, list1.at(0), "[first element should be 1]", "AT {1}");
TEST<bool>::ASSERT_EQUAL(2, list1.at(1), "[first element should be 2]", "AT {2}");
TEST<bool>::ASSERT_EQUAL(3, list1.at(2), "[first element should be 3]", "AT {3}");
TEST<bool>::ASSERT_EQUAL(4, list1.at(3), "[first element should be 4]", "AT {4}");
TEST<bool>::ASSERT_EQUAL(5, list1.at(4), "[first element should be 5]", "AT {5}");
}
/**
* Tests the delete_at() method of a single_l list.
* @param None
* @return None
* @throws None
*/
void test_delete_at() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
list1.delete_at(4);
TEST<bool>::ASSERT_FALSE(list1.at(4) == 50, "[element should not be 50]", "DELETE_AT {1}");
TEST<bool>::ASSERT_TRUE(list1.size() == 9, "[size should be 9]", "DELETE_AT {2}");
list1.delete_at(3);
TEST<bool>::ASSERT_FALSE(list1.at(3) == 40, "[element should not be 40]", "DELETE_AT {3}");
TEST<bool>::ASSERT_TRUE(list1.size() == 8, "[size should be 8]", "DELETE_AT {4}");
TEST<bool>::ASSERT_TRUE(list1.at(3) == 60, "[list1 at i = 3 should be 60]", "DELETE_AT {4}");
}
/**
* Tests the delete_value method of the single_l class.
* @param None
* @return None
* @throws None
*/
void test_delete_value() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
list1.delete_value(10);
TEST<bool>::ASSERT_EQUAL(-1, list1.search(100), "[100 should not be in the list]", "DELETE_VAL {1}");
list1.delete_value(90);
TEST<bool>::ASSERT_EQUAL(-1, list1.search(90), "[90 should not be in the list]", "DELETE_VAL {2}");
list1.delete_value(80);
TEST<bool>::ASSERT_EQUAL(-1, list1.search(80), "[80 should not be in the list]", "DELETE_VAL {3}");
TEST<bool>::ASSERT(list1.size() == 7, "[size should be 7]", "DELETE_VAL {4}");
}
/**
* Tests the search method of the single_l class.
* @return void
* @throws None
*/
void test_search() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(0, list1.search(10), "[10 should be at i = 0]", "SEARCH {1}");
TEST<bool>::ASSERT_EQUAL(5, list1.search(60), "[60 should be at i = 5]", "SEARCH {2}");
TEST<bool>::ASSERT_EQUAL(-1, list1.search(120), "[120 should not be int the list]", "SEARCH {3}");
TEST<bool>::ASSERT_EQUAL(-1, list1.search(8), "[8 should not be int the list]", "SEARCH {4}");
}
/**
* Tests the at_position() function of the single_l class.
* @param None
* @return None
* @throws None
*/
void test_at_position() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(1, list1.search(20), "[20 should be at i = 1]", "AT_POSITION {1}");
TEST<bool>::ASSERT_EQUAL(2, list1.search(30), "[30 should be at i = 2]", "AT_POSITION {2}");
list1.at_position(12, 2);
TEST<bool>::ASSERT_EQUAL(2, list1.search(12), "[12 should be at i = 2]", "AT_POSITION {3}");
TEST<bool>::ASSERT_EQUAL(1, list1.search(20), "[20 should be at i = 1]", "AT_POSITION {4}");
TEST<bool>::ASSERT_EQUAL(3, list1.search(30), "[30 should be at i = 3]", "AT_POSITION {5}");
TEST<bool>::ASSERT(list1.size() == 11, "[size should be 11]", "AT_POSITION {6}");
}
/**
* Tests the reverse function of single_l class.
* @param None
* @return None
* @throws None
*/
void test_reverse() {
single::single_l<int> list1{10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
TEST<bool>::ASSERT_EQUAL(10, list1[0], "[first element should be 10]", "REVERSE {1}");
TEST<bool>::ASSERT_EQUAL(100, list1[list1.size()-1], "[last element should be 100]", "REVERSE {2}");
list1.reverse();
TEST<bool>::ASSERT_EQUAL(100, list1[0], "[first element should be 100]", "REVERSE {3}");
TEST<bool>::ASSERT_EQUAL(10, list1[list1.size()-1], "[last element should be 10]", "REVERSE {4}");
}
/**
* Tests if the single linked list is properly sorted.
* @param None
* @return None
* @throws None
*/
void test_sort() {
single::single_l<int> list1{95, 7, 86, 44, 43, 63, 46, 45, 13, 2, 19, 4, 84, 100, 5, 63, 53, 56, 85, 95, 48, 88, 9, 6, 65};
list1.sort();
TEST<int>::GREATER(list1[1], list1[0], "[list should be dsorted]", "SORT {1}");
TEST<int>::GREATER(list1[2], list1[1], "[list should be dsorted]", "SORT {2}");
TEST<int>::GREATER(list1[3], list1[2], "[list should be dsorted]", "SORT {3}");
TEST<int>::GREATER(list1[4], list1[3], "[list should be dsorted]", "SORT {4}");
TEST<int>::SMALLER(list1[5], list1[6], "[list should be sorted]", "SORT {5}");
TEST<int>::SMALLER(list1[1], list1[10], "[list should be sorted]", "SORT {6}");
TEST<int>::SMALLER(list1[18], list1[22], "[list should be sorted]", "SORT {7}");
TEST<int>::SMALLER(list1[23], list1[24], "[list should be sorted]", "SORT {8}");
}
/**
* Tests the functionality of removing duplicates from a single linked list.
* @param None
* @return None
* @throws None
*/
void test_remove_duplicates() {
single::single_l<int> list1{1, 1, 1, 1, 1, 1, 1, 3};
list1.remove_duplicates();
TEST<bool>::ASSERT(list1.size() == 2, "[size should be 2]", "REM_DUP {1}");
TEST<bool>::ASSERT(list1[0] == 1, "[first element should be 1]", "REM_DUP {1}");
}
/**
* Tests resizing of single_l list.
* @param None
* @return None
* @throws None
*/
void test_resize() {
single::single_l<int> list1;
TEST<bool>::ASSERT(list1.size() == 0, "[size should be 0]", "RESIZE {1}" );
list1.resize(5);
TEST<bool>::ASSERT(list1.size() == 5, "[size should be 5]", "RESIZE {2}" );
list1.resize(10);
TEST<bool>::ASSERT(list1.size() == 10, "[size should be 10]", "RESIZE {3}" );
}
/**
* Solves the problem from my textbook described in main.cpp
* @param list1 first list to be merged
* @param list2 second list to be merged
* @return a new single linked list that contains all elements from the input
* lists in non-decreasing order
* @throws None
*/
template <typename T>
single::single_l<T> merge_lists(const single::single_l<T>& list1, const single::single_l<T>& list2) {
single::single_l<T> merged_list;
single::single_l<T> temp_list1 = list1;
single::single_l<T> temp_list2 = list2;
while (!temp_list1.empty() && !temp_list2.empty()) {
if (temp_list1.front() <= temp_list2.front()) {
merged_list.push_back(temp_list1.front());
temp_list1.pop_front();
} else {
merged_list.push_back(temp_list2.front());
temp_list2.pop_front();
}
}
while (!temp_list1.empty()) {
merged_list.push_back(temp_list1.front());
temp_list1.pop_front();
}
while (!temp_list2.empty()) {
merged_list.push_back(temp_list2.front());
temp_list2.pop_front();
}
return merged_list;
}
/**
* Runs an example function.
* @param None
* @return None
* @throws None
*/
void run_example() {
single::single_l<int> list1{1, 2, 3, 4, 5, 6};
single::single_l<int> list2{7, 8, 9, 10, 11};
std::cout << "\n[first list]: ";
list1.print();
std::cout << "\n[second list]: ";
list2.print();
single::single_l<int> merged_list = merge_lists(list1, list2);
std::cout << "\n[merged list]: ";
merged_list.print();
std::cout << "\n";
}
/**
* Runs a series of tests for the LinkedList class to ensure proper functionality.
* @return true if all tests pass, false otherwise
* @throws std::exception if any test fails
*/
bool run_tests() {
try {
test_constructor();
std::cout << std::endl;
test_destructor();
std::cout << std::endl;
test_move_constructor();
std::cout << std::endl;
test_move_assignment_operator();
std::cout << std::endl;
test_copy_constructor();
std::cout << std::endl;
test_copy_assignment_operator();
std::cout << std::endl;
test_push_back();
std::cout << std::endl;
test_push_front();
std::cout << std::endl;
test_pop_back();
std::cout << std::endl;
test_pop_front();
std::cout << std::endl;
test_clear();
std::cout << std::endl;
test_swap();
std::cout << std::endl;
test_empty();
std::cout << std::endl;
test_size();
std::cout << std::endl;
test_front();
std::cout << std::endl;
test_back();
std::cout << std::endl;
test_front_const();
std::cout << std::endl;
test_back_const();
std::cout << std::endl;
test_sub_operator();
std::cout << std::endl;
test_at();
std::cout << std::endl;
test_delete_at();
std::cout << std::endl;
test_delete_value();
std::cout << std::endl;
test_search();
std::cout << std::endl;
test_at_position();
std::cout << std::endl;
test_reverse();
std::cout << std::endl;
test_sort();
std::cout << std::endl;
test_remove_duplicates();
std::cout << std::endl;
test_resize();
std::cout << std::endl;
}catch (std::exception &e) {
std::cerr << "{error}: " << e.what() << std::endl;
return false;
}
return true;
}
test.h
//
// test.h
// linked_list_xcode
//
// Created by Jakob Balkovec on 14/06/2023.
//
// {
// This file defines a series of test functions implemented in @file test_file.h
// }
#ifndef test_h
#define test_h
#include "single_l.h"
void test_constructor();
void test_destructor();
void test_move_constructor();
void test_move_assignment_operator();
void test_copy_constructor();
void test_copy_assignment_operator();
void test_push_back();
void test_push_front();
void test_pop_back();
void test_pop_front();
void test_clear();
void test_swap();
void test_empty();
void test_size();
void test_front();
void test_back();
void test_front_const();
void test_back_const();
void test_sub_operator();
void test_at();
void test_delete_at();
void test_delete_value();
void test_search();
void test_at_position();
void test_reverse();
void test_sort();
void test_remove_duplicates();
void test_resize();
template <typename T>
single::single_l<T> merge_lists();
void run_example();
bool run_tests();
#endif /* test_h */
MakeFile
CC = g++
CFLAGS = -std=c++17 -Wall
all: prog
prog: single_l.o main.o test_file.o
$(CC) $(CFLAGS) $^ -o $@
single_l.o: single_l.cpp single_l.h
$(CC) $(CFLAGS) -c $<
main.o: main.cpp single_l.h test_file.h test.h
$(CC) $(CFLAGS) -c $<
test_file.o: test_file.cpp test_file.h single_l.h test.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o prog