28#include <unordered_map>
29#include <unordered_set>
33#include "iceberg/result.h"
34#include "iceberg/util/executor.h"
35#include "iceberg/util/macros.h"
36#include "iceberg/util/task_group.h"
40template <
typename T,
auto... Options>
45template <
typename Ref>
46using ParallelCollectArgT =
47 std::conditional_t<std::is_lvalue_reference_v<Ref>, Ref, std::remove_cvref_t<Ref>&&>;
49template <
typename T,
auto... Options>
51 typename ParallelReduce<T, Options...>::result_type;
53 ParallelReduce<T, Options...>::Reduce(values)
54 } -> std::same_as<
typename ParallelReduce<T, Options...>::result_type>;
57template <std::ranges::input_range InputRange,
typename Task>
58using ParallelCollectValueT = ResultValueT<std::invoke_result_t<
59 std::remove_reference_t<Task>&,
60 ParallelCollectArgT<std::ranges::range_reference_t<InputRange>>>>;
62template <std::size_t I,
typename... Args>
64 using args_tuple_type = std::tuple<Args&&...>;
65 using input_type = std::tuple_element_t<I * 2, args_tuple_type>;
66 using task_type = std::tuple_element_t<I * 2 + 1, args_tuple_type>;
67 using value_type = ParallelCollectValueT<input_type, task_type>;
70template <
typename InputRange,
typename Task,
auto... Options>
72 std::ranges::forward_range<InputRange> && std::ranges::sized_range<InputRange> &&
73 (std::is_lvalue_reference_v<std::ranges::range_reference_t<InputRange>> ||
74 std::constructible_from<
75 std::remove_cvref_t<std::ranges::range_reference_t<InputRange>>,
76 std::ranges::range_reference_t<InputRange>>) &&
77 requires(std::remove_reference_t<Task>& task,
78 ParallelCollectArgT<std::ranges::range_reference_t<InputRange>> item) {
79 { std::invoke(task, std::forward<
decltype(item)>(item)) } ->
AsResult;
80 requires(!std::same_as<void, ParallelCollectValueT<InputRange, Task>>);
81 requires std::default_initializable<ParallelCollectValueT<InputRange, Task>>;
87template <
typename... Args>
89 using result_type = std::unordered_set<Args...>;
91 template <std::ranges::input_range Values>
92 static result_type Reduce(Values&& values) {
94 for (
auto&& value : values) {
101template <
typename... Args>
103 using result_type = std::vector<Args...>;
105 template <std::ranges::input_range Values>
106 static result_type Reduce(Values&& values) {
107 return std::forward<Values>(values) | std::views::join | std::views::as_rvalue |
108 std::ranges::to<result_type>();
112template <
typename K,
typename... VectorArgs,
typename... MapArgs>
114 using result_type = std::unordered_map<K, std::vector<VectorArgs...>, MapArgs...>;
116 template <std::ranges::input_range Values>
117 static result_type Reduce(Values&& values) {
119 for (
auto&& value : values) {
121 for (
auto& [key, entries] : value) {
122 auto& out = result[key];
123 out.insert(out.end(), std::make_move_iterator(entries.begin()),
124 std::make_move_iterator(entries.end()));
131template <
typename First,
typename Second>
133 using result_type = std::pair<typename ParallelReduce<First>::result_type,
136 template <std::ranges::forward_range Values>
137 static result_type Reduce(Values&& values) {
143template <
typename... Ts>
145 using result_type = std::tuple<typename ParallelReduce<Ts>::result_type...>;
147 template <std::ranges::forward_range Values>
148 static result_type Reduce(Values&& values) {
149 return Reduce(values, std::index_sequence_for<Ts...>{});
153 template <std::ranges::forward_range Values, std::size_t... I>
154 static result_type Reduce(Values&& values, std::index_sequence<I...>) {
155 return result_type{
ParallelReduce<std::tuple_element_t<I, std::tuple<Ts...>>>::Reduce(
156 values | std::views::elements<I>)...};
160template <
auto... Options,
typename... Args>
161 requires(
sizeof...(Args) >= 2 &&
sizeof...(Args) % 2 == 0 &&
162 []<std::size_t... I>(std::index_sequence<I...>)
consteval {
164 typename internal::ParallelCollectTraits<I, Args...>::input_type,
165 typename internal::ParallelCollectTraits<I, Args...>::task_type,
168 }(std::make_index_sequence<
sizeof...(Args) / 2>{}))
169auto ParallelCollect(OptionalExecutor executor, Args&&... args) {
170 constexpr std::size_t pair_count =
sizeof...(Args) / 2;
171 using indices = std::make_index_sequence<pair_count>;
173 auto args_tuple = std::forward_as_tuple(std::forward<Args>(args)...);
175 auto values_tuple = [&]<std::size_t... I>(std::index_sequence<I...>) {
176 return std::tuple{[&] {
177 using traits = internal::ParallelCollectTraits<I, Args...>;
179 return std::vector<typename traits::value_type>(
180 std::ranges::size(std::get<I * 2>(args_tuple)));
184 auto reduce_all = [&]<std::size_t... I>(std::index_sequence<I...>) {
185 auto reduce_one = [&]<std::size_t PairIndex> {
186 using traits = internal::ParallelCollectTraits<PairIndex, Args...>;
187 using value_type =
typename traits::value_type;
188 return ParallelReduce<value_type, Options...>::Reduce(
189 std::get<PairIndex>(values_tuple));
192 if constexpr (pair_count == 1) {
193 return reduce_one.template operator()<0>();
195 return std::tuple{reduce_one.template operator()<I>()...};
199 using result_type =
decltype(reduce_all(indices{}));
202 group.SetExecutor(executor);
204 [&]<std::size_t... I>(std::index_sequence<I...>) {
207 using item_ref = std::ranges::range_reference_t<
208 typename internal::ParallelCollectTraits<I, Args...>::input_type>;
210 for (
auto&& [item, value] :
211 std::views::zip(
std::get<I * 2>(args_tuple),
std::get<I>(values_tuple))) {
212 if constexpr (std::is_lvalue_reference_v<item_ref>) {
213 group.Submit([&]() -> Status {
214 ICEBERG_ASSIGN_OR_RAISE(
215 value, std::invoke(std::get<I * 2 + 1>(args_tuple), item));
219 group.Submit([&, item = std::move(item)]()
mutable -> Status {
220 ICEBERG_ASSIGN_OR_RAISE(
221 value, std::invoke(std::get<I * 2 + 1>(args_tuple), std::move(item)));
230 auto status = std::move(group).Run();
231 if (!status.has_value()) {
232 return Result<result_type>(std::unexpected<Error>(status.error()));
235 return Result<result_type>(reduce_all(indices{}));
Definition executor_util_internal.h:71
Definition executor_util_internal.h:50
Definition executor_util_internal.h:41
Definition executor_util_internal.h:63