Browse Source

algorithm: radix sort

Michele Caini 6 years ago
parent
commit
b8471e3cf1
4 changed files with 155 additions and 2 deletions
  1. 69 2
      src/entt/core/algorithm.hpp
  2. 15 0
      src/entt/core/utility.hpp
  3. 64 0
      test/entt/core/algorithm.cpp
  4. 7 0
      test/entt/core/utility.cpp

+ 69 - 2
src/entt/core/algorithm.hpp

@@ -2,9 +2,11 @@
 #define ENTT_CORE_ALGORITHM_HPP
 
 
-#include <functional>
-#include <algorithm>
 #include <utility>
+#include <iterator>
+#include <algorithm>
+#include <functional>
+#include "utility.hpp"
 
 
 namespace entt {
@@ -70,6 +72,71 @@ struct insertion_sort {
 };
 
 
+/**
+ * @brief Function object for performing LSD radix sort.
+ * @tparam Bit Number of bits processed per pass.
+ * @tparam N Maximum number of bits to sort.
+ */
+template<std::size_t Bit, std::size_t N>
+struct radix_sort {
+    static_assert((N % Bit) == 0);
+
+    /**
+     * @brief Sorts the elements in a range.
+     *
+     * Sorts the elements in a range using the given _getter_ to access the
+     * actual data to be sorted.
+     *
+     * This implementation is inspired by the online book
+     * [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
+     *
+     * @tparam It Type of random access iterator.
+     * @tparam Getter Type of _getter_ function object.
+     * @param first An iterator to the first element of the range to sort.
+     * @param last An iterator past the last element of the range to sort.
+     * @param getter A valid _getter_ function object.
+     */
+    template<typename It, typename Getter = identity>
+    void operator()(It first, It last, Getter getter = Getter{}) const {
+        if(first < last) {
+            static constexpr auto mask = (1 << Bit) - 1;
+            static constexpr auto buckets = 1 << Bit;
+            static constexpr auto passes = N / Bit;
+
+            using value_type = typename std::iterator_traits<It>::value_type;
+            std::vector<value_type> aux(std::distance(first, last));
+
+            auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) {
+                std::size_t index[buckets]{};
+                std::size_t count[buckets]{};
+
+                std::for_each(from, to, [&getter, &count, start](const value_type &item) {
+                    ++count[(getter(item) >> start) & mask];
+                });
+
+                std::for_each(std::next(std::begin(index)), std::end(index), [index = std::begin(index), count = std::begin(count)](auto &item) mutable {
+                    item = *(index++) + *(count++);
+                });
+
+                std::for_each(from, to, [&getter, &out, &index, start](value_type &item) {
+                    out[index[(getter(item) >> start) & mask]++] = std::move(item);
+                });
+            };
+
+            for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) {
+                part(first, last, aux.begin(), pass * Bit);
+                part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
+            }
+
+            if constexpr(passes & 1) {
+                part(first, last, aux.begin(), (passes - 1) * Bit);
+                std::move(aux.begin(), aux.end(), first);
+            }
+        }
+    }
+};
+
+
 }
 
 

+ 15 - 0
src/entt/core/utility.hpp

@@ -8,6 +8,21 @@
 namespace entt {
 
 
+/*! @brief Identity function object (waiting for C++20). */
+struct identity {
+    /**
+     * @brief Returns its argument unchanged.
+     * @tparam Type Type of the argument.
+     * @param value The actual argument.
+     * @return The submitted value as-is.
+     */
+    template<class Type>
+    constexpr Type && operator()(Type &&value) const ENTT_NOEXCEPT {
+        return std::forward<Type>(value);
+    }
+};
+
+
 /**
  * @brief Constant utility to disambiguate overloaded member functions.
  * @tparam Type Function type of the desired overload.

+ 64 - 0
test/entt/core/algorithm.cpp

@@ -2,6 +2,10 @@
 #include <gtest/gtest.h>
 #include <entt/core/algorithm.hpp>
 
+struct boxed_int {
+    int value;
+};
+
 TEST(Algorithm, StdSort) {
     // well, I'm pretty sure it works, it's std::sort!!
     std::array<int, 5> arr{{4, 1, 3, 2, 0}};
@@ -14,6 +18,20 @@ TEST(Algorithm, StdSort) {
     }
 }
 
+TEST(Algorithm, StdSortBoxedInt) {
+    // well, I'm pretty sure it works, it's std::sort!!
+    std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
+    entt::std_sort sort;
+
+    sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
+        return lhs.value > rhs.value;
+    });
+
+    for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
+        ASSERT_GT(arr[i].value, arr[i+1].value);
+    }
+}
+
 TEST(Algorithm, InsertionSort) {
     std::array<int, 5> arr{{4, 1, 3, 2, 0}};
     entt::insertion_sort sort;
@@ -25,9 +43,55 @@ TEST(Algorithm, InsertionSort) {
     }
 }
 
+TEST(Algorithm, InsertionSortBoxedInt) {
+    std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
+    entt::insertion_sort sort;
+
+    sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) {
+        return lhs.value > rhs.value;
+    });
+
+    for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
+        ASSERT_GT(arr[i].value, arr[i+1].value);
+    }
+}
+
 TEST(Algorithm, InsertionSortEmptyContainer) {
     std::vector<int> vec{};
     entt::insertion_sort sort;
     // this should crash with asan enabled if we break the constraint
     sort(vec.begin(), vec.end());
 }
+
+TEST(Algorithm, RadixSort) {
+    std::array<uint32_t, 5> arr{{4, 1, 3, 2, 0}};
+    entt::radix_sort<8, 32> sort;
+
+    sort(arr.begin(), arr.end(), [](const auto &value) {
+        return value;
+    });
+
+    for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
+        ASSERT_LT(arr[i], arr[i+1]);
+    }
+}
+
+TEST(Algorithm, RadixSortBoxedInt) {
+    std::array<boxed_int, 6> arr{{{4}, {1}, {3}, {2}, {0}, {6}}};
+    entt::radix_sort<2, 6> sort;
+
+    sort(arr.rbegin(), arr.rend(), [](const auto &instance) {
+        return instance.value;
+    });
+
+    for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) {
+        ASSERT_GT(arr[i].value, arr[i+1].value);
+    }
+}
+
+TEST(Algorithm, RadixSortEmptyContainer) {
+    std::vector<int> vec{};
+    entt::radix_sort<8, 32> sort;
+    // this should crash with asan enabled if we break the constraint
+    sort(vec.begin(), vec.end());
+}

+ 7 - 0
test/entt/core/utility.cpp

@@ -9,6 +9,13 @@ struct Functions {
     void bar() {}
 };
 
+TEST(Utility, Identity) {
+    entt::identity identity;
+    int value = 42;
+
+    ASSERT_EQ(identity(value), value);
+    ASSERT_EQ(&identity(value), &value);
+}
 
 TEST(Utility, Overload) {
     ASSERT_EQ(entt::overload<void(int)>(&Functions::foo), static_cast<void(*)(int)>(&Functions::foo));