From ce03a24f88aceb1e2c26d8f5c0f59229b12f1d29 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Mon, 9 Sep 2019 10:22:36 +1000 Subject: [PATCH] list/sort: add initial linked list merge sort --- CMakeLists.txt | 2 + list/sort.hpp | 125 +++++++++++++++++++++++++++++++++++++++++++++ test/list/sort.cpp | 90 ++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 list/sort.hpp create mode 100644 test/list/sort.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 718a91b0..addd9270 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/list/sort.hpp b/list/sort.hpp new file mode 100644 index 00000000..034f65f8 --- /dev/null +++ b/list/sort.hpp @@ -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 + */ + +#pragma once + +#include "../debug/assert.hpp" + +#include +#include + +namespace cruft::list { + template + struct node { + node *next; + ValueT data; + + bool operator< (node const &rhs) const + { + return data < rhs.data; + } + }; + + + template + node* + midpoint (node *first, node *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 + node* + merge ( + node *first, + node *middle, + node *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 *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 + node* + sort (node *first, node *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 + bool is_sorted (node 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; + } +} diff --git a/test/list/sort.cpp b/test/list/sort.cpp new file mode 100644 index 00000000..3524cbf6 --- /dev/null +++ b/test/list/sort.cpp @@ -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 + */ + +#include "list/sort.hpp" +#include "random.hpp" +#include "tap.hpp" +#include "iterator/zip.hpp" + +#include +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +template +static void +test_sort ( + cruft::TAP::logger &tap, + std::vector &&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 + > 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 (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 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 data (COUNT, COUNT); + test_sort (tap, std::move (data), "sorting small identical array"); + } + + + return tap.status (); +} \ No newline at end of file