Skip to content

Commit

Permalink
feat: algo/mapping/cycle_finder.hpp
Browse files Browse the repository at this point in the history
  • Loading branch information
arumakan1727 committed Aug 1, 2023
1 parent 1c4e242 commit 6a17ae2
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/armkn/algo/mapping/cycle_finder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once
#include <cstddef>
#include <type_traits>
#include <utility>
#include <vector>

/// Floyd's Tortoise and Hare Algorithm
template <class T>
class CycleFinder {
uint32_t _until_cycle_start;
uint32_t _cycle_len;
std::vector<T> nf; // nf[i] := `start` から `f` を `i` 回適用した結果

public:
CycleFinder() = default;

template <class Fn, std::enable_if_t<std::is_invocable_r_v<T, Fn, T>, std::nullptr_t> = nullptr>
CycleFinder(T start, Fn&& f, std::size_t capacity = 512) : _until_cycle_start(0), _cycle_len(0) {
T tortoise = start, hare = start;
do {
tortoise = f(tortoise);
hare = f(f(hare));
} while (tortoise != hare);

nf.reserve(capacity);
nf.push_back(start);
tortoise = start;
while (tortoise != hare) {
hare = f(hare);
tortoise = f(tortoise);
nf.push_back(tortoise);
++_until_cycle_start;
}

do {
tortoise = f(tortoise);
nf.push_back(tortoise);
++_cycle_len;
} while (tortoise != hare);
}

/// mapping[x] を写像 f(x) とみなす
template <
class IndexAccessible,
class = std::void_t<decltype(std::declval<IndexAccessible>()[0])>>
CycleFinder(T start, IndexAccessible const& mapping)
: CycleFinder(
start,
[&](T x) { return (T)mapping[x]; },
mapping.size() + 1
) {}

inline uint32_t until_cycle_start() const {
return _until_cycle_start;
}

inline uint32_t cycle_len() const {
return _cycle_len;
}

/// start に f を n 回適用した結果を O(1) で求める。
inline const T apply_repeat(uint64_t n) const {
if (n <= _until_cycle_start + _cycle_len) return nf[n];
return nf[_until_cycle_start + (n - _until_cycle_start) % _cycle_len];
}
};
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
add_subdirectory(lib/Catch2)

add_executable(tests
armkn/algo/mapping/cycle_finder.cpp
armkn/dp/k_choose_prod_sum.cpp
armkn/dsu/union_find.cpp
armkn/fmt/printer.cpp
Expand Down
76 changes: 76 additions & 0 deletions tests/armkn/algo/mapping/cycle_finder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include <armkn/algo/mapping/cycle_finder.hpp>
#include <catch2/catch_test_macros.hpp>

#include <vector>

TEST_CASE("CycleFinder") {
std::vector<int> f(7);
f[0] = 1;
f[1] = 2;
f[2] = 3;
f[3] = 4;
f[4] = 5;
f[5] = 3;
f[6] = 6;

SECTION("start=1") {
auto cf = CycleFinder(1, f);
CHECK(cf.until_cycle_start() == 2);
CHECK(cf.cycle_len() == 3);
CHECK(cf.apply_repeat(0) == 1);
CHECK(cf.apply_repeat(1) == 2);
CHECK(cf.apply_repeat(2) == 3);
CHECK(cf.apply_repeat(3) == 4);
CHECK(cf.apply_repeat(4) == 5);
CHECK(cf.apply_repeat(5) == 3);
CHECK(cf.apply_repeat(6) == 4);
CHECK(cf.apply_repeat(7) == 5);
CHECK(cf.apply_repeat(8) == 3);
CHECK(cf.apply_repeat(9) == 4);
CHECK(cf.apply_repeat(10) == 5);
CHECK(cf.apply_repeat(11) == 3);
}
SECTION("start=3") {
auto cf = CycleFinder(3, f);
CHECK(cf.until_cycle_start() == 0);
CHECK(cf.cycle_len() == 3);
CHECK(cf.apply_repeat(0) == 3);
CHECK(cf.apply_repeat(1) == 4);
CHECK(cf.apply_repeat(2) == 5);
CHECK(cf.apply_repeat(3) == 3);
CHECK(cf.apply_repeat(4) == 4);
CHECK(cf.apply_repeat(5) == 5);
CHECK(cf.apply_repeat(6) == 3);
CHECK(cf.apply_repeat(7) == 4);
CHECK(cf.apply_repeat(8) == 5);
CHECK(cf.apply_repeat(9) == 3);
CHECK(cf.apply_repeat(10) == 4);
CHECK(cf.apply_repeat(11) == 5);
}
SECTION("start=4") {
auto cf = CycleFinder(4, f);
CHECK(cf.until_cycle_start() == 0);
CHECK(cf.cycle_len() == 3);
CHECK(cf.apply_repeat(0) == 4);
CHECK(cf.apply_repeat(1) == 5);
CHECK(cf.apply_repeat(2) == 3);
CHECK(cf.apply_repeat(3) == 4);
CHECK(cf.apply_repeat(4) == 5);
CHECK(cf.apply_repeat(5) == 3);
CHECK(cf.apply_repeat(6) == 4);
CHECK(cf.apply_repeat(7) == 5);
CHECK(cf.apply_repeat(8) == 3);
CHECK(cf.apply_repeat(9) == 4);
CHECK(cf.apply_repeat(10) == 5);
CHECK(cf.apply_repeat(11) == 3);
}
SECTION("start=6") {
auto cf = CycleFinder(6, f);
CHECK(cf.until_cycle_start() == 0);
CHECK(cf.cycle_len() == 1);
CHECK(cf.apply_repeat(0) == 6);
CHECK(cf.apply_repeat(1) == 6);
CHECK(cf.apply_repeat(2) == 6);
CHECK(cf.apply_repeat(3) == 6);
}
}

0 comments on commit 6a17ae2

Please sign in to comment.