diff --git a/.circleci/tests.unit1.algorithms b/.circleci/tests.unit1.algorithms index d35e591525da..66c4f0f179a3 100644 --- a/.circleci/tests.unit1.algorithms +++ b/.circleci/tests.unit1.algorithms @@ -10,6 +10,7 @@ tests.unit.modules.algorithms.algorithms.adjacentfind tests.unit.modules.algorithms.algorithms.adjacentfind_binary tests.unit.modules.algorithms.algorithms.all_of tests.unit.modules.algorithms.algorithms.any_of +tests.unit.modules.algorithms.algorithms.contains tests.unit.modules.algorithms.algorithms.copy tests.unit.modules.algorithms.algorithms.copyif_random tests.unit.modules.algorithms.algorithms.copyif_forward diff --git a/libs/core/algorithms/CMakeLists.txt b/libs/core/algorithms/CMakeLists.txt index 6d10912d4d63..97328782bd9c 100644 --- a/libs/core/algorithms/CMakeLists.txt +++ b/libs/core/algorithms/CMakeLists.txt @@ -15,6 +15,7 @@ set(algorithms_headers hpx/parallel/algorithms/adjacent_difference.hpp hpx/parallel/algorithms/adjacent_find.hpp hpx/parallel/algorithms/all_any_none.hpp + hpx/parallel/algorithms/contains.hpp hpx/parallel/algorithms/copy.hpp hpx/parallel/algorithms/count.hpp hpx/parallel/algorithms/destroy.hpp @@ -23,6 +24,7 @@ set(algorithms_headers hpx/parallel/algorithms/detail/accumulate.hpp hpx/parallel/algorithms/detail/advance_and_get_distance.hpp hpx/parallel/algorithms/detail/advance_to_sentinel.hpp + hpx/parallel/algorithms/detail/contains.hpp hpx/parallel/algorithms/detail/dispatch.hpp hpx/parallel/algorithms/detail/distance.hpp hpx/parallel/algorithms/detail/equal.hpp diff --git a/libs/core/algorithms/include/hpx/parallel/algorithm.hpp b/libs/core/algorithms/include/hpx/parallel/algorithm.hpp index 4aedd3d820c7..309cdf842b25 100644 --- a/libs/core/algorithms/include/hpx/parallel/algorithm.hpp +++ b/libs/core/algorithms/include/hpx/parallel/algorithm.hpp @@ -16,6 +16,7 @@ // Parallelism TS V1 #include #include +#include #include #include #include diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/contains.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/contains.hpp new file mode 100644 index 000000000000..ea7d6b63f200 --- /dev/null +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/contains.hpp @@ -0,0 +1,130 @@ +// Copyright (c) 2024 Zakaria Abdi +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace hpx::parallel { namespace detail { + struct contains : public algorithm + { + constexpr contains() noexcept + : algorithm("contains") + { + } + + template + static constexpr bool sequential( + ExPolicy, Iterator first, Sentinel last, const T& val, Proj&& proj) + { + return sequential_contains>( + first, last, val, HPX_FORWARD(Proj, proj)); + } + + template + static util::detail::algorithm_result_t parallel( + ExPolicy&& orgpolicy, Iterator first, Sentinel last, const T& val, + Proj&& proj) + { + const std::size_t count = detail::distance(first, last); + if (count <= 0) + return util::detail::algorithm_result::get( + false); + + decltype(auto) policy = parallel::util::adapt_placement_mode( + HPX_FORWARD(ExPolicy, orgpolicy), + hpx::threads::thread_placement_hint::breadth_first); + + using policy_type = std::decay_t; + util::cancellation_token<> tok; + auto f1 = [val, tok, proj](Iterator first, std::size_t count) { + sequential_contains(first, val, count, tok, proj); + return tok.was_cancelled(); + }; + + auto f2 = [](auto&& results) { + return std::any_of(hpx::util::begin(results), + hpx::util::end(results), + [](hpx::future& val) { return val.get(); }); + }; + + return util::partitioner::call( + HPX_FORWARD(decltype(policy), policy), first, count, + HPX_MOVE(f1), HPX_MOVE(f2)); + } + }; +}} // namespace hpx::parallel::detail +namespace hpx { + + inline constexpr struct contains_t final + : hpx::functional::detail::tag_fallback + { + private: + template && hpx:: + traits::is_iterator_v&& hpx::is_invocable_v::value_type>)> + + friend bool tag_fallback_invoke(hpx::contains_t, Iterator first, + Sentinel last, const T& val, Proj&& proj = Proj()) + { + static_assert(hpx::traits::is_input_iterator_v, + "Required at least input iterator."); + + static_assert(hpx::traits::is_input_iterator_v, + "Required at least input iterator."); + + return hpx::parallel::detail::contains().call( + hpx::execution::seq, first, last, val, proj); + } + + template && hpx::traits::is_iterator_v&& hpx::traits:: + is_iterator_v&& hpx::is_invocable_v::value_type>)> + + friend typename parallel::util::detail::algorithm_result::type + tag_fallback_invoke(hpx::contains_t, ExPolicy&& policy, Iterator first, + Sentinel last, T const& val, Proj&& proj = Proj()) + { + static_assert(hpx::traits::is_iterator_v, + "Required at least iterator."); + + static_assert(hpx::traits::is_iterator_v, + "Required at least iterator."); + + return hpx::parallel::detail::contains().call( + HPX_FORWARD(ExPolicy, policy), first, last, val, + HPX_FORWARD(Proj, proj)); + } + + } contains{}; +} // namespace hpx diff --git a/libs/core/algorithms/include/hpx/parallel/algorithms/detail/contains.hpp b/libs/core/algorithms/include/hpx/parallel/algorithms/detail/contains.hpp new file mode 100644 index 000000000000..02cbccdc1981 --- /dev/null +++ b/libs/core/algorithms/include/hpx/parallel/algorithms/detail/contains.hpp @@ -0,0 +1,61 @@ +// Copyright (c) 2024 Zakaria Abdi +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace hpx::parallel::detail { + + template + struct sequential_contains_t final + : hpx::functional::detail::tag_fallback> + { + private: + template + friend constexpr bool tag_fallback_invoke(sequential_contains_t, + Iterator first, Sentinel last, const T& val, Proj&& proj) + { + const std::size_t distance = detail::distance(first, last); + if (distance <= 0) + return false; + + const auto itr = + util::loop_pred>( + first, last, [&val, &proj](const auto& cur) { + return HPX_INVOKE(proj, *cur) == val; + }); + + return itr != last; + } + + template + friend constexpr void tag_fallback_invoke(sequential_contains_t, + Iterator first, T const& val, std::size_t count, Token& tok, + Proj&& proj) + { + util::loop_n( + first, count, tok, [&val, &tok, &proj](const auto& cur) { + if (HPX_INVOKE(proj, *cur) == val) + { + tok.cancel(); + return; + } + }); + } + }; + template + inline constexpr sequential_contains_t sequential_contains = + sequential_contains_t{}; +} //namespace hpx::parallel::detail diff --git a/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt b/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt index c5d009222d47..d31db556e772 100644 --- a/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt +++ b/libs/core/algorithms/tests/unit/algorithms/CMakeLists.txt @@ -23,6 +23,7 @@ set(tests adjacentfind_binary all_of any_of + contains copy copyif_random copyif_forward diff --git a/libs/core/algorithms/tests/unit/algorithms/contains.cpp b/libs/core/algorithms/tests/unit/algorithms/contains.cpp new file mode 100644 index 000000000000..ef5f4250a847 --- /dev/null +++ b/libs/core/algorithms/tests/unit/algorithms/contains.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2024 Zakaria Abdi +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include + +#include +#include "contains_tests.hpp" + +//////////////////////////////////////////////////////////////////////////// +template +void test_contains() +{ + using namespace hpx::execution; + + test_contains(IteratorTag()); + + test_contains(seq, IteratorTag()); + test_contains(par, IteratorTag()); + test_contains(par_unseq, IteratorTag()); + + test_contains_async(seq(task), IteratorTag()); + test_contains_async(par(task), IteratorTag()); + test_contains_async(par_unseq(task), IteratorTag()); +} + +void contains_test() +{ + test_contains(); + test_contains(); +} + +//////////////////////////////////////////////////////////////////////////// +template +void test_contains_exception() +{ + using namespace hpx::execution; + + test_contains_exception(IteratorTag()); + + test_contains_exception(seq, IteratorTag()); + test_contains_exception(par, IteratorTag()); + + test_contains_exception_async(seq(task), IteratorTag()); + test_contains_exception_async(par(task), IteratorTag()); +} + +void contains_exception_test() +{ + test_contains_exception(); + test_contains_exception(); +} + +//////////////////////////////////////////////////////////////////////////// +template +void test_contains_bad_alloc() +{ + using namespace hpx::execution; + + test_contains_bad_alloc(seq, IteratorTag()); + test_contains_bad_alloc(par, IteratorTag()); + + test_contains_bad_alloc_async(seq(task), IteratorTag()); + test_contains_bad_alloc_async(par(task), IteratorTag()); +} + +void contains_bad_alloc_test() +{ + test_contains_bad_alloc(); + test_contains_bad_alloc(); +} + +int hpx_main(hpx::program_options::variables_map& vm) +{ + if (vm.count("seed")) + seed = vm["seed"].as(); + + std::cout << "Using seed as: " << seed << std::endl; + gen.seed(seed); + + contains_test(); + contains_exception_test(); + contains_bad_alloc_test(); + + return hpx::local::finalize(); +} + +int main(int argc, char* argv[]) +{ + using namespace hpx::program_options; + options_description desc_commandline( + "Usage" HPX_APPLICATION_STRING " [options]"); + + desc_commandline.add_options()("seed,s", value(), + " the random number generator to use for this run"); + + std::vector cfg = {"hpx.os_threads=all"}; + + hpx::local::init_params init_args; + init_args.desc_cmdline = desc_commandline; + init_args.cfg = cfg; + + HPX_TEST_EQ_MSG(hpx::local::init(hpx_main, argc, argv, init_args), 0, + "HPX main exited with non-zero status"); + + return hpx::util::report_errors(); +} diff --git a/libs/core/algorithms/tests/unit/algorithms/contains_tests.hpp b/libs/core/algorithms/tests/unit/algorithms/contains_tests.hpp new file mode 100644 index 000000000000..8d0505d204a3 --- /dev/null +++ b/libs/core/algorithms/tests/unit/algorithms/contains_tests.hpp @@ -0,0 +1,385 @@ +// Copyright (c) 2024 Zakaria Abdi +// SPDX-License-Identifier: BSL-1.0 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "test_utils.hpp" + +////////////////////////////////////////////////////////////////////////////// +unsigned int seed = std::random_device{}(); +std::mt19937 gen(seed); +std::uniform_int_distribution<> dis(2, 101); + +template +void test_contains(IteratorTag) +{ + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + std::vector c(10007); + std::fill(std::begin(c), std::end(c), dis(gen)); + const std::size_t n = c.size(); + c.at(n / 2) = 1; + + bool result1 = + hpx::contains(iterator(std::begin(c)), iterator(std::end(c)), int(1)); + HPX_TEST_EQ(result1, true); + + bool result2 = + hpx::contains(iterator(std::begin(c)), iterator(std::end(c)), int(110)); + HPX_TEST_EQ(result2, false); +} + +template +void test_contains(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + std::vector c(10007); + std::fill(std::begin(c), std::end(c), dis(gen)); + const std::size_t n = c.size(); + c.at(n / 2) = 1; + + bool result1 = + hpx::contains(policy, iterator(std::begin(c)), std::end(c), int(1)); + HPX_TEST_EQ(result1, true); + + bool result2 = + hpx::contains(policy, iterator(std::begin(c)), std::end(c), int(110)); + HPX_TEST_EQ(result2, false); +} + +template +void test_contains_explicit_sender_direct( + Policy l, ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + namespace ex = hpx::execution::experimental; + namespace tt = hpx::this_thread::experimental; + + std::vector c(10007); + std::fill(std::begin(c), std::end(c), dis(gen)); + const std::size_t n = c.size(); + c.at(n / 2) = 1; + + using scheduler_t = ex::thread_pool_policy_scheduler; + auto exec = ex::explicit_scheduler_executor(scheduler_t(l)); + + bool result1 = hpx::contains(policy.on(exec), iterator(std::begin(c)), + iterator(std::end(c)), int(1)); + HPX_TEST_EQ(result1, true); + + bool result2 = hpx::contains(policy.on(exec), iterator(std::begin(c)), + iterator(std::end(c)), int(110)); + HPX_TEST_EQ(result2, false); +} + +template +void test_contains_explicit_sender(Policy l, ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + namespace ex = hpx::execution::experimental; + namespace tt = hpx::this_thread::experimental; + + std::vector c(10007); + const std::size_t n = c.size(); + std::fill(std::begin(c), std::end(c), dis(gen)); + c.at(n / 2) = 1; + + using scheduler_t = ex::thread_pool_policy_scheduler; + auto exec = ex::explicit_scheduler_executor(scheduler_t(l)); + auto result1 = ex::just(iterator(std::begin(c)), iterator(std::end(c)), + int(1) | hpx::contains(policy.on(exec)) | tt::sync_wait()); + + HPX_TEST_EQ(hpx::get<0>(*result1), true); + + auto result2 = ex::just(iterator(std::begin(c)), iterator(std::end(c)), + int(110) | hpx::contains(policy.on(exec)) | tt::sync_wait()); + + HPX_TEST_EQ(hpx::get<0>(*result2), false); +} + +template +void test_contains_async(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + std::vector c(10007); + const std::size_t n = c.size(); + std::fill(std::begin(c), std::end(c), dis(gen)); + c.at(n / 2) = 1; + + hpx::future result1 = hpx::contains( + policy, iterator(std::begin(c)), iterator(std::end(c)), int(1)); + result1.wait(); + HPX_TEST_EQ(result1.get(), true); + + hpx::future result2 = hpx::contains( + policy, iterator(std::begin(c)), iterator(std::end(c)), int(110)); + result2.wait(); + HPX_TEST_EQ(result2.get(), false); +} + +template +void test_contains_explicit_sender_direct_async( + Policy l, ExPolicy&& p, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::test_iterator iterator; + + namespace ex = hpx::execution::experimental; + namespace tt = hpx::this_thread::experimental; + + std::vector c(10007); + const std::size_t n = c.size(); + std::fill(std::begin(c), std::end(c), dis(gen)); + c.at(n / 2) = 1; + + using scheduler_t = ex::thread_pool_policy_scheduler; + auto exec = ex::explicit_scheduler_executor(scheduler_t(l)); + + auto result1 = hpx::contains(p.on(exec), iterator(std::begin(c)), + iterator(std::end(c)), int(1)) | + tt::sync_wait(); + HPX_TEST_EQ(hpx::get<0>(*result1), true); + + auto result2 = hpx::contains(p.on(exec), iterator(std::begin(c)), + iterator(std::end(c)), int(110)) | + tt::sync_wait(); + HPX_TEST_EQ(hpx::get<0>(*result2), false); +} + +/////////////////////////////////////////////////////////////////////////////// +template +void test_contains_exception(IteratorTag) +{ + typedef std::vector::iterator base_iterator; + typedef test::decorated_iterator + decorated_iterator; + std::vector c(10007); + std::iota(std::begin(c), std::end(c), gen() + 1); + const std::size_t n = c.size(); + c.at(n / 2) = 0; + bool caught_exception = false; + try + { + hpx::contains( + decorated_iterator(std::begin(c)), + []() { throw std::runtime_error("test"); }, + decorated_iterator(std::end(c)), int(0)); + + HPX_TEST(false); + } + + catch (hpx::exception_list const& e) + { + caught_exception = true; + test::test_num_exceptions::call(hpx::execution::seq, e); + } + + catch (...) + { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); +} + +template +void test_contains_exception(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + std::vector c(10007); + const std::size_t n = c.size(); + std::iota(std::begin(c), std::end(c), gen() + 1); + c.at(n / 2) = 0; + + bool caught_exception = false; + try + { + hpx::contains( + policy, decorated_iterator(std::begin(c)), + []() { throw std::runtime_error("test"); }, + decorated_iterator(std::end(c)), int(0)); + + HPX_TEST(false); + } + + catch (hpx::exception_list const& e) + { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + + catch (...) + { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); +} + +template +void test_contains_exception_async(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + std::vector c(10007); + const std::size_t n = c.size(); + std::iota(std::begin(c), std::end(c), gen() + 1); + c.at(n / 2) = 0; + + bool caught_exception = false; + bool returned_from_algorithm = false; + try + { + hpx::future result = hpx::contains( + policy, decorated_iterator(std::begin(c)), + []() { throw std::runtime_error("test"); }, + decorated_iterator(std::end(c)), int(0)); + returned_from_algorithm = true; + result.get(); + HPX_TEST(false); + } + + catch (hpx::exception_list const& e) + { + caught_exception = true; + test::test_num_exceptions::call(policy, e); + } + + catch (...) + { + HPX_TEST(false); + } + + HPX_TEST(caught_exception); + HPX_TEST(returned_from_algorithm); +} + +////////////////////////////////////////////////////////////////////////////// + +template +void test_contains_bad_alloc(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_b"); + + typedef std::vector::iterator base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + std::vector c(10007); + const std::size_t n = c.size(); + std::iota(std::begin(c), std::end(c), gen() + 1); + c.at(n / 2) = 0; + bool caught_bad_alloc = false; + try + { + hpx::contains(policy, decorated_iterator(std::begin(c)), + []() { throw std::bad_alloc(); }), + decorated_iterator(std::end(c), int(0)); + HPX_TEST(false); + } + + catch (std::bad_alloc const&) + { + caught_bad_alloc = true; + } + + catch (...) + { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); +} + +template +void test_contains_bad_alloc_async(ExPolicy&& policy, IteratorTag) +{ + static_assert(hpx::is_execution_policy_v, + "hpx::is_execution_policy_v"); + + typedef std::vector::iterator base_iterator; + typedef test::decorated_iterator + decorated_iterator; + + std::vector c(10007); + const std::size_t n = c.size(); + std::iota(std::begin(c), std::end(c), gen() + 1); + c.at(n / 2) = 0; + bool caught_bad_alloc = false; + bool returned_from_algorithm = false; + try + { + hpx::future result = hpx::contains( + policy, decorated_iterator(std::begin(c)), + []() { throw std::bad_alloc(); }, decorated_iterator(std::end(c)), + int(0)); + returned_from_algorithm = true; + result.get(); + HPX_TEST(false); + } + + catch (std::bad_alloc const&) + { + caught_bad_alloc = true; + } + + catch (...) + { + HPX_TEST(false); + } + + HPX_TEST(caught_bad_alloc); + HPX_TEST(returned_from_algorithm); +}