algo/sort: add SOA sorting helper
sort::soa applies a permutation to multiple value arrays such that the first value array is sorted
This commit is contained in:
parent
f0dd072d16
commit
1ad3e0bde0
@ -146,6 +146,9 @@ list (
|
||||
hash/md2.cpp
|
||||
adapter.hpp
|
||||
adapter.cpp
|
||||
algo/sort.cpp
|
||||
algo/sort.hpp
|
||||
algo/sort.ipp
|
||||
alloc/fwd.hpp
|
||||
alloc/affix.cpp
|
||||
alloc/affix.hpp
|
||||
@ -432,6 +435,7 @@ if (TESTS)
|
||||
|
||||
list (
|
||||
APPEND TEST_BIN
|
||||
algo/sort
|
||||
alloc/aligned
|
||||
alloc/arena
|
||||
alloc/dynamic
|
||||
|
26
algo/sort.cpp
Normal file
26
algo/sort.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Copyright:
|
||||
* 2015-2016, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#include "sort.hpp"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// instantiate the soa sorting function as a compilation check.
|
||||
//
|
||||
// this particular instantiation isn't necessarily required by any user, it's
|
||||
// just convenient.
|
||||
template void cruft::util::sort::soa (int*, int*, bool (*)(int,int), double*);
|
49
algo/sort.hpp
Normal file
49
algo/sort.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Copyright:
|
||||
* 2017, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#ifndef CRUFT_UTIL_ALGO_SORT_HPP
|
||||
#define CRUFT_UTIL_ALGO_SORT_HPP
|
||||
|
||||
namespace cruft::util::sort {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// 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);
|
||||
}
|
||||
|
||||
#include "./sort.ipp"
|
||||
|
||||
#endif
|
117
algo/sort.ipp
Normal file
117
algo/sort.ipp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Copyright:
|
||||
* 2017, Danny Robson <danny@nerdcruft.net>
|
||||
*/
|
||||
|
||||
#ifdef CRUFT_UTIL_ALGO_SORT_IPP
|
||||
#error
|
||||
#endif
|
||||
|
||||
#define CRUFT_UTIL_ALGO_SORT_IPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "../debug.hpp"
|
||||
#include "../tuple.hpp"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace cruft::util::sort::detail {
|
||||
template <typename IndexA, typename IndexB, typename RandomIt>
|
||||
void
|
||||
index_swap (IndexA a, IndexB b, RandomIt 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, std::forward<Tail> (tail)...);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename IndexIt, typename ValueIt, typename ...OtherIt>
|
||||
void
|
||||
cruft::util::sort::reorder (IndexIt idx_first,
|
||||
IndexIt idx_last,
|
||||
ValueIt value,
|
||||
OtherIt ...tail)
|
||||
{
|
||||
// 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) {
|
||||
while (i != (decltype(size))idx_first[i]) {
|
||||
auto j = idx_first[i];
|
||||
|
||||
detail::index_swap (i, j, value, std::forward<OtherIt> (tail)..., idx_first);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template <typename RandomIt, class Comparator, class ...Args>
|
||||
void
|
||||
cruft::util::sort::soa (RandomIt key_first,
|
||||
RandomIt key_last,
|
||||
Comparator comp,
|
||||
Args ...values)
|
||||
{
|
||||
// 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)
|
||||
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);
|
||||
|
||||
std::sort (std::begin (indices),
|
||||
std::end (indices),
|
||||
[&] (const auto &a, const auto &b)
|
||||
{
|
||||
return comp (key_first[a], key_first[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 < (decltype(size))dest.size (); ++i) {
|
||||
dest[indices[i]] = i;
|
||||
}
|
||||
|
||||
// reorder all the arrays using the mapping we have discovered.
|
||||
reorder (std::begin (dest), std::end (dest), key_first, std::forward<Args> (values)...);
|
||||
};
|
121
test/algo/sort.cpp
Normal file
121
test/algo/sort.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include "algo/sort.hpp"
|
||||
#include "tap.hpp"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
|
||||
|
||||
void
|
||||
reorder (std::vector<int> &values, std::vector<int> &indices)
|
||||
{
|
||||
CHECK_EQ (values.size (), indices.size ());
|
||||
if (indices.size () <= 1)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < indices.size () - 1; ++i) {
|
||||
// check if this item is in the correct slot
|
||||
while ((int)i != indices[i]) {
|
||||
auto alt = indices[i];
|
||||
|
||||
// swap this item with the one that's occupying its intended slot
|
||||
std::swap (values[i], values[alt]);
|
||||
//std::swap (*(values... + i), *)
|
||||
std::swap (indices[i], indices[alt]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
{
|
||||
util::TAP::logger tap;
|
||||
|
||||
// Check that reorder works with a single value array
|
||||
{
|
||||
std::array<int,3> indices { 2, 0, 1 };
|
||||
std::array<int,3> values { 3, 4, 5 };
|
||||
const std::array<int,3> expected = { 4, 5, 3 };
|
||||
|
||||
cruft::util::sort::reorder (
|
||||
std::begin (indices), std::end (indices),
|
||||
std::begin (values)
|
||||
);
|
||||
|
||||
tap.expect_eq (expected, values, "reorder with one array");
|
||||
}
|
||||
|
||||
// Check that reorder works with multiple value arrays
|
||||
{
|
||||
std::array<int,3> indices { 2, 0, 1 };
|
||||
std::array<int,3> values[] = {
|
||||
{ 3, 4, 5 },
|
||||
{ 8, 7, 6 },
|
||||
{ 11, 9, 10 },
|
||||
};
|
||||
const std::array<int,3> expected[] = {
|
||||
{ 4, 5, 3 },
|
||||
{ 7, 6, 8 },
|
||||
{ 9, 10, 11 },
|
||||
};
|
||||
|
||||
cruft::util::sort::reorder (
|
||||
std::begin (indices), std::end (indices),
|
||||
std::begin (values[0]),
|
||||
std::begin (values[1]),
|
||||
std::begin (values[2])
|
||||
);
|
||||
|
||||
tap.expect (values[0] == expected[0] &&
|
||||
values[1] == expected[1] &&
|
||||
values[2] == expected[2],
|
||||
"reorder with three arrays"
|
||||
);
|
||||
}
|
||||
|
||||
// Check that SOA sort works with a single value array
|
||||
static struct {
|
||||
std::vector<int> values;
|
||||
const char *msg;
|
||||
} TESTS[] = {
|
||||
{ { 0 }, "single value, single array" },
|
||||
{ { 1, 0 }, "two values, single array" },
|
||||
{ { 5, 0, 4, 1, 2, 3 }, "many values, single array" },
|
||||
};
|
||||
|
||||
for (auto &t: TESTS) {
|
||||
cruft::util::sort::soa (
|
||||
std::begin (t.values), std::end (t.values),
|
||||
[] (int a, int b) { return a < b; }
|
||||
);
|
||||
|
||||
tap.expect (std::is_sorted (std::cbegin (t.values), std::cend (t.values)), "%s", t.msg);
|
||||
}
|
||||
|
||||
// Check that SOA sort works with multiple value arrays
|
||||
{
|
||||
std::array<unsigned, 3> value_i { 2, 0, 1 };
|
||||
std::array<char, 3> value_c { 'a', 'b', 'c' };
|
||||
std::array<float,3> value_f { 0.f, 1.f, 2.f };
|
||||
|
||||
const std::array<unsigned,3> expected_i { 0, 1, 2 };
|
||||
const std::array<char,3> expected_c { 'b', 'c', 'a' };
|
||||
const std::array<float,3> expected_f { 1.f, 2.f, 0.f };
|
||||
|
||||
cruft::util::sort::soa (
|
||||
std::begin (value_i), std::end (value_i),
|
||||
[] (const auto &i, const auto &j) { return i < j; },
|
||||
std::begin (value_c),
|
||||
std::begin (value_f)
|
||||
);
|
||||
|
||||
tap.expect (
|
||||
value_i == expected_i &&
|
||||
value_c == expected_c &&
|
||||
value_f == expected_f,
|
||||
"SOA sort with 3 arrays"
|
||||
);
|
||||
}
|
||||
|
||||
return tap.status ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user