I am trying to practice C++, so I decided to implement canonical algorithms in C++ as a way to learn best practices and idioms of the language.
I started with merge sort. How could I improve this routine?
I especially want to focus on ways of better using the features of C++. I've also included my tests, which I've written with the catch framework.
To run the test, just include the file available here https://github.com/catchorg/Catch2/releases/download/v2.13.8/catch.hpp
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
std::vector<int> merge (std::vector<int> left, std::vector<int> right) {
int num_terms = left.size() + right.size();
std::vector<int> out = {};
int l = 0, r = 0;
for (int i = 0; i < num_terms; i++) {
if (left[l] > right[r]) {
out.push_back(right[r]);
r++;
} else {
out.push_back(left[l]);
l++;
}
if (l == left.size()) {
while (r < right.size()) {
out.push_back(right[r++]);
}
break;
}
if (r == right.size()) {
while (l < left.size()) {
out.push_back(left[l++]);
}
break;
}
}
return out;
}
std::vector<int> merge_sort (std::vector<int> unsorted) {
if (unsorted.size() <= 1) {
return unsorted;
}
// recursively split vector into two vectors
int middle = unsorted.size() / 2;
std::vector<int> left = { unsorted.begin(), unsorted.begin() + middle };
std::vector<int> right = { unsorted.begin() + middle, unsorted.end() };
return merge(merge_sort(left), merge_sort(right));
}
TEST_CASE( "sorts lists of numbers", "[merge_sort]" ) {
SECTION( "non-repeating") {
std::vector<int> sorted = {1, 2, 3, 4, 5};
std::vector<int> unsorted = {5, 2, 4, 3, 1 };
REQUIRE(merge_sort(unsorted) == sorted);
}
SECTION("repeating") {
std::vector<int> sorted = {1, 1, 2, 3, 4, 5, 6};
std::vector<int> unsorted = {4, 1, 2, 1, 3, 6, 5};
REQUIRE(merge_sort(unsorted) == sorted);
}
SECTION( "sorts lists with odd number of elements") {
std::vector<int> sorted = {1, 2, 3, 4, 5, 6, 7, 8, 9};
std::vector<int> unsorted = {4, 7, 5, 6, 8, 2, 9, 1, 3 };
REQUIRE(merge_sort(unsorted) == sorted);
}
SECTION("handles lists with two elements") {
std::vector<int> sorted = {2, 8};
std::vector<int> unsorted = {8, 2};
REQUIRE(merge_sort(unsorted) == sorted);
}
SECTION("handles lists with one element") {
std::vector<int> sorted = {8};
std::vector<int> unsorted = {8};
REQUIRE(merge_sort(unsorted) == sorted);
}
SECTION("handles empty lists") {
std::vector<int> sorted = {};
std::vector<int> unsorted = {};
REQUIRE(merge_sort(unsorted) == sorted);
}
}
TEST_CASE("merge merges sorted arrays", "[merge]") {
SECTION("merges sorted vectors of even length") {
std::vector<int> left = {1, 3, 5, 7};
std::vector<int> right = {2, 4, 6, 8};
std::vector<int> merged = {1, 2, 3, 4, 5, 6, 7, 8 };
REQUIRE(merge(left, right) == merged);
}
SECTION("uneven length (l > r)") {
std::vector<int> left = {1, 3, 5, 7};
std::vector<int> right = {2, 4, 6 };
std::vector<int> merged = {1, 2, 3, 4, 5, 6, 7 };
REQUIRE(merge(left, right) == merged);
}
SECTION("uneven length (r > l)") {
std::vector<int> left = {2, 5};
std::vector<int> right = {1, 3, 4};
std::vector<int> merged = {1, 2, 3, 4, 5};
REQUIRE(merge(left, right) == merged);
}
SECTION("duplicate elements") {
std::vector<int> left = {2, 2, 5};
std::vector<int> right = {1, 3, 3, 4};
std::vector<int> merged = {1, 2, 2, 3, 3, 4, 5};
REQUIRE(merge(left, right) == merged);
}
SECTION("one half emptying first") {
std::vector<int> left = {4, 6};
std::vector<int> right = {1, 3 };
std::vector<int> merged = {1, 3, 4, 6};
REQUIRE(merge(left, right) == merged);
}
SECTION("input of two elements") {
std::vector<int> left = {8};
std::vector<int> right = {2};
std::vector<int> merged = {2, 8};
REQUIRE(merge(left, right) == merged);
}
}