diff --git a/docs/reference/adaptors.rst b/docs/reference/adaptors.rst index ea0f6d76..21236813 100644 --- a/docs/reference/adaptors.rst +++ b/docs/reference/adaptors.rst @@ -1081,6 +1081,18 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o :see also: * :func:`flux::filter` +``permutations`` +^^^^^^^^^^^^^^^^ + +.. function:: + template + requires(not infinite_sequence) + auto permutations(Seq seq) -> bidirectional_sequence auto + + :param seq: A non-infinite sequence + + :returns: A bidirectional sequence yielding permutations of :var:`seq`. + ``pairwise`` ^^^^^^^^^^^^ @@ -1997,4 +2009,4 @@ You can pass a reference to a sequence into an adaptor using :func:`flux::ref` o * - :concept:`read_only_sequence` - All inputs are read-only * - :concept:`const_iterable_sequence` - - All inputs are const-iterable \ No newline at end of file + - All inputs are const-iterable diff --git a/include/flux/adaptor/permutations.hpp b/include/flux/adaptor/permutations.hpp new file mode 100644 index 00000000..181225c0 --- /dev/null +++ b/include/flux/adaptor/permutations.hpp @@ -0,0 +1,124 @@ +// Copyright stuff here? Not really sure on that as I've never done +// it + +#ifndef FLUX_ADAPTOR_PERMUTATIONS_HPP_INCLUDED +#define FLUX_ADAPTOR_PERMUTATIONS_HPP_INCLUDED + +#include "flux/core/concepts.hpp" +#include "flux/core/inline_sequence_base.hpp" +#include "flux/adaptor/permutations_base.hpp" +#include "flux/adaptor/cartesian_base.hpp" // tuple_repeated_t +#include +#include +#include +#include + +namespace flux { +namespace detail { + +template + requires(SubsequenceSize > 0) && flux::bounded_sequence && flux::multipass_sequence +struct permutations_adaptor + : public flux::inline_sequence_base> { +private: + Base base_; + +public: + constexpr permutations_adaptor(Base&& base) : base_(std::move(base)) { } + + struct flux_sequence_traits : flux::default_sequence_traits { + private: + using self_t = permutations_adaptor; + + struct cursor_type { + std::size_t permutation_index_; + + [[nodiscard]] constexpr auto operator<=>(const cursor_type& other) const + { + return permutation_index_ <=> other.permutation_index_; + } + [[nodiscard]] constexpr bool operator==(const cursor_type& other) const + { + return permutation_index_ == other.permutation_index_; + } + }; + + public: + // TODO: remove vector type + using value_type = tuple_repeated_t, SubsequenceSize>; + + inline static constexpr bool is_infinite = false; + + static constexpr auto first(self_t& self) -> cursor_type + { + return {.permutation_index_ = 0}; + } + + static constexpr auto is_last(self_t& self, const cursor_type& cursor) -> bool + { + return false; + } + + static constexpr auto inc(self_t& self, cursor_type& cursor) -> void + { + /* + const auto k = SubsequenceSize; + const auto n = self.cache_.size(); + + cursor.permutation_index_ += 1; + + for (const auto i : flux::iota(std::size_t {0}, std::size_t {k}).reverse()) { + if (cursor.cycles_[i] == 0) { + cursor.cycles_[i] = n - i - 1; + std::rotate(cursor.indices_.begin() + i, cursor.indices_.begin() + i + 1, + cursor.indices_.end()); + } else { + const auto swap_index = n - cursor.cycles_[i]; + std::swap(cursor.indices_[i], cursor.indices_[swap_index]); + cursor.cycles_[i] -= 1; + return; + } + } + */ + } + + static constexpr auto read_at(self_t& self, const cursor_type& cursor) -> value_type + { + return {}; + } + }; +}; + +template + requires(SubsequenceSize > 0) +struct permutations_fn { + template + requires(not infinite_sequence) + [[nodiscard]] + constexpr auto operator()(Seq&& seq) const -> auto + { + return permutations_adaptor, SubsequenceSize>(FLUX_FWD(seq)); + } +}; + +} // namespace detail + +FLUX_EXPORT +template +inline constexpr auto permutations = detail::permutations_fn {}; + +// clang-format off +FLUX_EXPORT +template +template + requires(SubsequenceSize > 0) +constexpr auto inline_sequence_base::permutations() && + requires(not infinite_sequence) +{ + return flux::permutations(std::move(derived())); +} +// clang-format on +// +} // namespace flux + +#endif diff --git a/include/flux/adaptor/permutations_base.hpp b/include/flux/adaptor/permutations_base.hpp new file mode 100644 index 00000000..19bbfafd --- /dev/null +++ b/include/flux/adaptor/permutations_base.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +namespace flux::detail { + +// Calculates the factorial of `x` +[[nodiscard]] constexpr auto factorial(const std::integral auto x) -> decltype(x) +{ + if (x <= 1) { + return 1; + } + return x * factorial(x - 1); +} + +// TODO: this will need to change to support something other than vectors. +// Given an input vector and a range of indices, return a new vector with the same values +// of `input`, ordered by `indices` up to the given `length`. +template +[[nodiscard]] constexpr auto reindex_vec(const std::vector& input, const auto& indices, + const std::size_t length) -> std::vector +{ + std::vector output; + output.reserve(input.size()); + + for (std::size_t i = 0; i < length; i++) { + output.push_back(input[indices[i]]); + } + + return output; +} + +// TODO: this will need to change to support something other than vectors. +// Given an input vector and a range of indices, return a new vector with the same values +// of `input`, ordered by `indices`. +template +[[nodiscard]] constexpr auto reindex_vec(const std::vector& input, const auto& indices) + -> std::vector +{ + return reindex_vec(input, indices, input.size()); +} +} // namespace flux::detail diff --git a/include/flux/core/inline_sequence_base.hpp b/include/flux/core/inline_sequence_base.hpp index 735287d7..35d2dee2 100644 --- a/include/flux/core/inline_sequence_base.hpp +++ b/include/flux/core/inline_sequence_base.hpp @@ -6,6 +6,7 @@ #ifndef FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED #define FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED +#include "flux/core/concepts.hpp" #include #include @@ -314,6 +315,11 @@ struct inline_sequence_base { [[nodiscard]] constexpr auto mask(Mask&& mask_) &&; + template + requires(Size > 0) + [[nodiscard]] + constexpr auto permutations() && requires (not infinite_sequence); + [[nodiscard]] constexpr auto pairwise() && requires multipass_sequence; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ab21d420..43c58697 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(test-flux test_mask.cpp test_minmax.cpp test_output_to.cpp + test_permutations.cpp test_range_iface.cpp test_read_only.cpp test_reverse.cpp diff --git a/test/test_permutations.cpp b/test/test_permutations.cpp new file mode 100644 index 00000000..875b7caf --- /dev/null +++ b/test/test_permutations.cpp @@ -0,0 +1,112 @@ +#include "flux/core/concepts.hpp" +#include "flux/core/ref.hpp" +#include "test_utils.hpp" +#include + +#ifdef USE_MODULES +import flux; +#else +# include +#endif + +namespace { + +// TODO: will need to add a test that tests permutations of a sequence of references + +auto test_permutations_types() -> bool +{ + auto arr = std::array {1, 2, 3}; + + auto seq = flux::mut_ref(arr).permutations<3>(); + + using SeqType = decltype(seq); + using CurType = flux::cursor_t; + + // Sequence + static_assert(flux::sequence); + static_assert(flux::multipass_sequence); + // static_assert(flux::bounded_sequence); + // static_assert(flux::sized_sequence); + static_assert(not flux::infinite_sequence); + static_assert(not flux::random_access_sequence); + static_assert(not flux::contiguous_sequence); + + // Cursors + static_assert(flux::regular_cursor); + static_assert(flux::ordered_cursor); + + // Elements + // static_assert(std::same_as, std::vector>); + // static_assert(std::same_as, std::vector>); + // static_assert(flux::random_access_sequence>); + // static_assert(flux::random_access_sequence>); + + return true; +} + +constexpr auto test_permutations() -> bool +{ + // Simple Array Comparison + { + auto arr = std::array {1, 2, 3}; + auto seq = flux::mut_ref(arr).permutations<3>(); + + // Sizes + // STATIC_CHECK(seq.size() == 6); + + auto cur = flux::first(seq); + auto test_comp = std::array {1, 2, 3}; + + // Forward Iteration Permutations + while (not flux::is_last(seq, cur)) { + // STATIC_CHECK(check_equal(flux::read_at(seq, cur), test_comp)); + + flux::inc(seq, cur); + std::ranges::next_permutation(test_comp); + } + } + + return true; +} + +constexpr auto compare_permutations_with_python_itertools() -> bool +{ + // "flux" string permutations + { + /* + # Python code to generate comparison output + from itertools import permutations + + perms = [''.join(p) for p in permutations("flux", 4)] + formatted = '{' + ', '.join(f'"{x}"' for x in perms) + '}' + print(formatted) + */ + + auto reference = std::array { + "flux", "flxu", "fulx", "fuxl", "fxlu", "fxul", "lfux", "lfxu", + "lufx", "luxf", "lxfu", "lxuf", "uflx", "ufxl", "ulfx", "ulxf", + "uxfl", "uxlf", "xflu", "xful", "xlfu", "xluf", "xufl", "xulf"}; + + auto str = std::string("flux"); + auto permutations = flux::permutations<4>(flux::ref(str)); + auto first = flux::first(permutations); + + for (auto i : flux::ints().take(24)) { + auto p = flux::read_at(permutations, first); + auto r = flux::ref(reference.at(static_cast(i))); + + // STATIC_CHECK(check_equal(p, r)); + + flux::inc(permutations, first); + } + } + return true; +} + +} // namespace + +TEST_CASE("permutations") +{ + auto implementation = false; + REQUIRE(implementation); +}