libcruft-util/cruft/util/algo/sort.hpp

185 lines
6.2 KiB
C++
Raw Permalink Normal View History

/*
2018-08-04 15:14:06 +10:00
* 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:
* 2017, Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_ALGO_SORT_HPP
#define CRUFT_UTIL_ALGO_SORT_HPP
#include "../debug/assert.hpp"
2018-05-03 18:32:08 +10:00
#include "../cast.hpp"
#include <iterator>
#include <algorithm>
#include <numeric>
#include <vector>
namespace cruft::sort {
namespace detail {
template <typename IndexA, typename IndexB, typename RandomIt>
void
index_swap (IndexA a, IndexB b, RandomIt value)
{
static_assert(
std::is_base_of<
std::random_access_iterator_tag,
typename std::iterator_traits<RandomIt>::iterator_category
>::value
);
std::swap (value[a], value[b]);
}
template <typename IndexA, typename IndexB, typename RandomIt, typename ...Tail>
void
index_swap (IndexA a, IndexB b, RandomIt value, Tail ...tail)
{
index_swap (a, b, value);
index_swap (a, b, tail...);
};
}
///////////////////////////////////////////////////////////////////////////
// rearrange the values in the arrays specified by the iterators value and
// ...tail by moving values to the positions described by the mapping of
// offset-to-dest_index in idx_first:idx_last.
//
// ie, the indices { 2, 1, 0 } will reverse a series of arrays of three
// elements
//
// all operations occur in-place, and the indices may be rearranged during
// the operation.
template <typename IndexIt, typename ValueIt, typename ...OtherIt>
void
reorder (IndexIt idx_first, IndexIt idx_last, ValueIt value, OtherIt ...tail)
{
static_assert (
std::is_base_of<
std::random_access_iterator_tag,
typename std::iterator_traits<IndexIt>::iterator_category
>::value
);
static_assert (
std::is_base_of<
std::random_access_iterator_tag,
typename std::iterator_traits<ValueIt>::iterator_category
>::value
);
// Bail early on zero size arrays, partly so we can simplify some
// debugging checks
auto size = std::distance (idx_first, idx_last);
if (!size)
return;
CHECK_LT (*std::max_element (idx_first, idx_last), size);
for (decltype(size) i = 0; i < size - 1; ++i) {
2018-05-03 18:32:08 +10:00
while (i != decltype(size){idx_first[i]}) {
auto j = idx_first[i];
detail::index_swap (i, j, value, tail..., idx_first);
}
}
}
///////////////////////////////////////////////////////////////////////////
/// Fill a supplied index buffer with a sorted range of indices over an
/// identically size collection of data elements addressed by the
/// RandomIterator `data`.
///
/// Does not modify any data element pointed to by `data`.
template <
typename IndexT,
typename RandomT,
typename ComparatorT = std::less<>
>
void
indices (
IndexT idx_first,
IndexT idx_last,
RandomT data,
ComparatorT &&cmp
) {
// initialise a monotonic sequence of indices
std::iota (idx_first, idx_last, 0);
// sort using the indices
std::sort (idx_first, idx_last, [&] (auto a, auto b) {
return cmp (
*(data + a),
*(data + b)
);
});
}
///////////////////////////////////////////////////////////////////////////
// sort an array specified by the iterators key_first:key_last using a
// comparator, and optionally a series of additional value iterators
// specified by ...values.
//
// sorting is performed in-place and will invariably allocate memory.
template <typename RandomIt, class Comparator, class ...Args>
void
soa (RandomIt key_first, RandomIt key_last, Comparator comp, Args ...values)
{
static_assert (
std::is_base_of<
std::random_access_iterator_tag,
typename std::iterator_traits<RandomIt>::iterator_category
>::value
);
// bail early on guaranteed sorted or degenerate cases. we can make some
// assumptions about minimum array sizes and non-wrapping indices later on
// this way.
if (std::distance (key_first, key_last) <= 1)
2018-05-03 18:32:08 +10:00
return;
// generate a list of indices into the key array and sort them so we have,
// in effect, a sorted list of pointers.
auto size = std::distance (key_first, key_last);
std::vector<decltype(size)> indices (size);
std::iota (std::begin (indices), std::end (indices), 0);
2018-05-03 18:32:08 +10:00
std::sort (
std::begin (indices),
std::end (indices),
2018-05-03 18:32:08 +10:00
[&] (const auto &cruft_util_sort_soa_a, const auto &cruft_util_sort_soa_b)
{
// GCC: we use the ridiculous parameter names to avoid a name aliasing
// bug/warning under gcc 6.3.0; if the client passes a comparator
// lambda that uses the same parameter names then a warning will be
// generated. given 'a' and 'b' aren't unlikely names we try to avoid
// them here.
return comp (
key_first[cruft_util_sort_soa_a],
key_first[cruft_util_sort_soa_b]
);
}
);
// convert from a sorted list of pointers to a mapping of pointers to
// desired final offsets. this is done so we can palm it off to the
// reorder function.
// TODO: avoid the need for this inverse array.
decltype (indices) dest (indices.size ());
for (decltype(size) i = 0; i < ::cruft::cast::sign<ssize_t> (dest.size ()); ++i) {
2018-05-03 18:32:08 +10:00
dest[indices[i]] = i;
}
// reorder all the arrays using the mapping we have discovered.
reorder (std::begin (dest), std::end (dest), key_first, values...);
}
}
#endif