list/sort: add initial linked list merge sort

This commit is contained in:
Danny Robson 2019-09-09 10:22:36 +10:00
parent 39759805ac
commit ce03a24f88
3 changed files with 217 additions and 0 deletions

View File

@ -419,6 +419,7 @@ list (
job/queue.hpp
kmeans.hpp
library.hpp
list/sort.hpp
log.cpp
log.hpp
map/fixed.cpp
@ -673,6 +674,7 @@ if (TESTS)
job/dispatch
job/queue
kmeans
list/sort
map/fixed
maths
maths/fast

125
list/sort.hpp Normal file
View File

@ -0,0 +1,125 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "../debug/assert.hpp"
#include <iterator>
#include <utility>
namespace cruft::list {
template <typename ValueT>
struct node {
node *next;
ValueT data;
bool operator< (node const &rhs) const
{
return data < rhs.data;
}
};
template <typename ValueT>
node<ValueT>*
midpoint (node<ValueT> *first, node<ValueT> *last)
{
auto slow = first;
auto fast = first->next;
while (fast != last->next) {
fast = fast->next;
if (fast == last->next)
return slow;
fast = fast->next;
slow = slow->next;
}
return slow;
}
template <typename ValueT>
node<ValueT>*
merge (
node<ValueT> *first,
node<ValueT> *middle,
node<ValueT> *last
) {
CHECK (first);
CHECK (middle);
CHECK (last);
if (first == last)
return first;
auto extract = [&] () {
auto &target = *first < *middle ? first : middle;
auto res = target;
target = target->next;
return res;
};
node<ValueT> *cursor = extract ();
auto head = cursor;
while (first && middle && first != middle && middle != last->next) {
auto target = extract ();
cursor->next = target;
cursor = target;
}
if (first == middle || !first)
cursor->next = middle;
else if (middle == last->next || !middle)
cursor->next = first;
return head;
}
// Merge sort for singly-linked lists
template <typename ValueT>
node<ValueT>*
sort (node<ValueT> *first, node<ValueT> *last)
{
if (first == last)
return first;
auto a_end = ::cruft::list::midpoint (first, last);
auto middle = a_end->next;
a_end->next = nullptr;
auto a = ::cruft::list::sort (first, a_end);
auto b = ::cruft::list::sort (middle, last);
return ::cruft::list::merge (a, b, last);
}
template <typename ValueT>
bool is_sorted (node<ValueT> const *head)
{
if (!head->next)
return true;
auto a = head;
auto b = head->next;
while (b) {
if (*b < *a)
return false;
a = a->next;
b = b->next;
}
return true;
}
}

90
test/list/sort.cpp Normal file
View File

@ -0,0 +1,90 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
*/
#include "list/sort.hpp"
#include "random.hpp"
#include "tap.hpp"
#include "iterator/zip.hpp"
#include <list>
#include <vector>
#include <algorithm>
///////////////////////////////////////////////////////////////////////////////
template <std::size_t N, typename ...ArgsT>
static void
test_sort (
cruft::TAP::logger &tap,
std::vector<int> &&data,
char const (&fmt)[N],
ArgsT&&...args
) {
// Pre-allocate the array so that we don't get caught by pointer
// invalidation as we build the nodes.
std::vector<
cruft::list::node<int>
> nodes (data.size ());
// Build the list and fold in the supplied data.
for (auto [node, value]: cruft::iterator::zip (nodes, data)) {
node.next = &node + 1;
node.data = value;
}
nodes.back ().next = nullptr;
// Actually do the sort and test the result.
auto head = cruft::list::sort (
&nodes.front (),
&nodes.back ()
);
tap.expect (
is_sorted (head),
fmt,
std::forward<ArgsT> (args)...
);
}
//-----------------------------------------------------------------------------
int main ()
{
cruft::TAP::logger tap;
// Try sorting a 'large' array of unique, but shuffled, contiguous integers.
{
// Don't use a power of two here. It won't exercise any edge cases in
// the splitting logic of some sorts (like merge).
static constexpr int COUNT = 500'000;
// Create a list of randomly distributed integers. Use a constant seed so
// we don't get randomised test results.
std::vector<int> data (COUNT);
std::iota (std::begin (data), std::end (data), 0);
std::shuffle (
std::begin (data),
std::end (data),
std::mt19937 {COUNT}
);
test_sort (tap, std::move (data), "sorting large unique and shuffled array");
}
// Try sorting a small array with identical elements
{
static constexpr int COUNT = 15;
std::vector<int> data (COUNT, COUNT);
test_sort (tap, std::move (data), "sorting small identical array");
}
return tap.status ();
}