Add tiny groups library with tests

This commit is contained in:
Nikita Lisitsa 2023-01-15 22:42:14 +03:00
parent 3ed7f142d7
commit 4c5d8777d6
5 changed files with 413 additions and 0 deletions

View file

@ -0,0 +1,7 @@
file(GLOB_RECURSE PSEMEK_GROUP_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
psemek_add_library(psemek-group ${PSEMEK_GROUP_HEADERS})
target_include_directories(psemek-group PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-group PUBLIC psemek-util)
psemek_glob_tests(psemek-group tests)

View file

@ -0,0 +1,124 @@
#pragma once
#include <psemek/util/range.hpp>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <limits>
namespace psemek::group
{
template <std::size_t N, typename Repr = std::size_t>
struct cyclic
{
static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
static_assert(std::numeric_limits<Repr>::max() > N);
static constexpr std::size_t size()
{
return N;
}
cyclic() = default;
static cyclic identity()
{
return cyclic{};
}
static cyclic rotation(Repr const & value)
{
return cyclic{value % static_cast<Repr>(N)};
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
static cyclic rotation(T const & value)
{
if (value >= 0)
return cyclic{static_cast<Repr>(static_cast<std::size_t>(value) % N)};
else
{
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
return cyclic{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
}
}
Repr value() const
{
return repr_;
}
struct value_iterator
{
using difference_type = int;
using value_type = cyclic<N, Repr>;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::forward_iterator_tag;
Repr repr;
cyclic<N, Repr> operator *() const
{
return cyclic<N, Repr>{repr};
}
value_iterator & operator++()
{
++repr;
return *this;
}
friend bool operator == (value_iterator const & it1, value_iterator const & it2)
{
return it1.repr == it2.repr;
}
};
static auto values()
{
return util::range{value_iterator{0}, value_iterator{N}};
}
private:
Repr repr_{0};
explicit cyclic(Repr const & repr)
: repr_(repr)
{}
};
template <std::size_t N, typename Repr>
bool operator == (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
{
return g1.value() == g2.value();
}
template <std::size_t N, typename Repr>
auto operator <=> (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
{
return g1.value() <=> g2.value();
}
template <std::size_t N, typename Repr>
cyclic<N, Repr> operator * (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
{
return cyclic<N, Repr>::rotation(g1.value() + g2.value());
}
template <std::size_t N, typename Repr>
cyclic<N, Repr> inverse(cyclic<N, Repr> const & g)
{
return cyclic<N, Repr>::rotation(N - g.value());
}
template <typename OStream, std::size_t N, typename Repr>
OStream & operator << (OStream & os, cyclic<N, Repr> const & g)
{
os << 'C' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
return os;
}
}

View file

@ -0,0 +1,177 @@
#pragma once
#include <psemek/group/cyclic.hpp>
#include <cstddef>
#include <type_traits>
#include <limits>
namespace psemek::group
{
template <std::size_t N, typename Repr = std::size_t>
struct dihedral
{
static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
static_assert(std::numeric_limits<Repr>::max() > N);
static constexpr std::size_t size()
{
return 2 * N;
}
dihedral() = default;
static dihedral identity()
{
return dihedral{};
}
static dihedral rotation(Repr const & repr)
{
return dihedral{repr % static_cast<Repr>(N)};
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
static dihedral rotation(T const & value)
{
if (value >= 0)
return dihedral{static_cast<Repr>(static_cast<std::size_t>(value) % N)};
else
{
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
}
}
template <typename Repr2>
static dihedral rotation(cyclic<N, Repr2> const & value)
{
return dihedral{static_cast<Repr>(value.repr())};
}
static dihedral reflection(Repr repr)
{
return dihedral{(repr % static_cast<Repr>(N)) + static_cast<Repr>(N)};
}
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
static dihedral reflection(T const & value)
{
if (value >= 0)
return dihedral{static_cast<Repr>((static_cast<std::size_t>(value) % N) + N)};
else
{
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(2 * N) : r + static_cast<std::int64_t>(N))};
}
}
Repr value() const
{
return repr_;
}
bool is_rotation() const
{
return repr_ < N;
}
bool is_reflection() const
{
return repr_ >= N;
}
struct value_iterator
{
using difference_type = int;
using value_type = dihedral<N, Repr>;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::forward_iterator_tag;
Repr repr;
dihedral<N, Repr> operator *() const
{
return dihedral<N, Repr>{repr};
}
value_iterator & operator++()
{
++repr;
return *this;
}
friend bool operator == (value_iterator const & it1, value_iterator const & it2)
{
return it1.repr == it2.repr;
}
};
static auto values()
{
return util::range{value_iterator{0}, value_iterator{2 * N}};
}
private:
Repr repr_{0};
explicit dihedral(Repr const & repr)
: repr_(repr)
{}
};
template <std::size_t N, typename Repr>
bool operator == (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
{
return g1.value() == g2.value();
}
template <std::size_t N, typename Repr>
auto operator <=> (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
{
return g1.value() <=> g2.value();
}
template <std::size_t N, typename Repr>
dihedral<N, Repr> operator * (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
{
if (g1.is_rotation())
{
auto repr = g1.value() + g2.value();
if (g2.is_rotation())
return dihedral<N, Repr>::rotation(repr);
else
return dihedral<N, Repr>::reflection(repr);
}
else
{
auto repr = g1.value() + static_cast<Repr>(N) - g2.value();
if (g2.is_rotation())
return dihedral<N, Repr>::reflection(repr);
else
return dihedral<N, Repr>::rotation(repr);
}
}
template <std::size_t N, typename Repr>
dihedral<N, Repr> inverse(dihedral<N, Repr> const & g)
{
if (g.is_rotation())
return dihedral<N, Repr>::rotation(N - g.value());
else
return g;
}
template <typename OStream, std::size_t N, typename Repr>
OStream & operator << (OStream & os, dihedral<N, Repr> const & g)
{
if (g.is_rotation())
os << 'D' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
else
os << 'D' << N << "(s" << (static_cast<std::size_t>(g.value()) % N) << ")";
return os;
}
}

View file

@ -0,0 +1,43 @@
#include <psemek/test/test.hpp>
#include <psemek/group/cyclic.hpp>
#include <set>
using namespace psemek::group;
using group_type = cyclic<7, std::uint8_t>;
test_case(group_cyclic_construct)
{
expect_equal(group_type::identity().value(), 0);
int const n = group_type::size();
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::rotation(i).value(), i % n);
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::rotation(i - 4 * n).value(), i % n);
}
test_case(group_cyclic_values)
{
std::set<group_type> values;
for (auto g : group_type::values())
values.insert(g);
expect_equal(values.size(), group_type::size());
}
test_case(group_cyclic_multiply)
{
for (auto g1 : group_type::values())
for (auto g2 : group_type::values())
expect_equal(g1 * g2, group_type::rotation(g1.value() + g2.value()));
}
test_case(group_cyclic_inverse)
{
for (auto g : group_type::values())
expect_equal(g * inverse(g), group_type::identity());
}

View file

@ -0,0 +1,62 @@
#include <psemek/test/test.hpp>
#include <psemek/group/dihedral.hpp>
#include <set>
using namespace psemek::group;
using group_type = dihedral<7, std::uint8_t>;
test_case(group_dihedral_construct)
{
expect_equal(group_type::identity().value(), 0);
int const n = group_type::size() / 2;
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::rotation(i).value(), i % n);
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::reflection(i).value(), (i % n) + n);
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::rotation(i - 4 * n).value(), i % n);
for (int i = 0; i < 4 * n; ++i)
expect_equal(group_type::reflection(i - 4 * n).value(), (i % n) + n);
}
test_case(group_dihedral_values)
{
std::set<group_type> values;
for (auto g : group_type::values())
values.insert(g);
expect_equal(values.size(), group_type::size());
}
test_case(group_dihedral_multiply)
{
for (auto g1 : group_type::values())
{
for (auto g2 : group_type::values())
{
auto value = g1.is_rotation() ? g1.value() + g2.value() : g1.value() + group_type::size() / 2 - g2.value();
if (g1.is_reflection() ^ g2.is_reflection())
{
expect_equal(g1 * g2, group_type::reflection(value));
}
else
{
expect_equal(g1 * g2, group_type::rotation(value));
}
}
}
}
test_case(group_dihedral_inverse)
{
for (auto g : group_type::values())
expect_equal(g * inverse(g), group_type::identity());
}