Skip to content

Commit db7adf0

Browse files
committed
tcbrindle#138 Add products<N> equivalent for cartesian_product(1, ..., N)
1 parent 29aae03 commit db7adf0

File tree

4 files changed

+690
-0
lines changed

4 files changed

+690
-0
lines changed

include/flux.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <flux/op/begin_end.hpp>
1515
#include <flux/op/cache_last.hpp>
1616
#include <flux/op/cartesian_product.hpp>
17+
#include <flux/op/products.hpp>
1718
#include <flux/op/cartesian_product_with.hpp>
1819
#include <flux/op/chain.hpp>
1920
#include <flux/op/chunk.hpp>

include/flux/op/products.hpp

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
2+
// Copyright (c) 2022 Tristan Brindle (tcbrindle at gmail dot com)
3+
// Copyright (c) 2023 NVIDIA Corporation (reply-to: [email protected])
4+
//
5+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7+
8+
#ifndef FLUX_OP_PRODUCT_HPP_INCLUDED
9+
#define FLUX_OP_PRODUCT_HPP_INCLUDED
10+
11+
#include <flux/core.hpp>
12+
#include <flux/core/numeric.hpp>
13+
#include <flux/op/from.hpp>
14+
15+
#include <tuple>
16+
17+
namespace flux {
18+
19+
namespace detail {
20+
21+
template <std::size_t RepeatCount, typename Base>
22+
struct product_traits_base;
23+
24+
template <std::size_t RepeatCount, sequence Base>
25+
struct product_adaptor
26+
: inline_sequence_base<product_adaptor<RepeatCount, Base>> {
27+
private:
28+
FLUX_NO_UNIQUE_ADDRESS Base base_;
29+
30+
friend struct product_traits_base<RepeatCount, Base>;
31+
friend struct sequence_traits<product_adaptor>;
32+
33+
public:
34+
static constexpr std::size_t count_ = RepeatCount;
35+
constexpr explicit product_adaptor(Base&& base)
36+
: base_(FLUX_FWD(base))
37+
{}
38+
};
39+
40+
41+
42+
template<std::size_t RepeatCount>
43+
struct product_fn {
44+
45+
template <adaptable_sequence Seq>
46+
requires multipass_sequence<Seq>
47+
[[nodiscard]]
48+
constexpr auto operator()(Seq&& seq) const
49+
{
50+
return product_adaptor<RepeatCount, std::decay_t<Seq>>(
51+
FLUX_FWD(seq));
52+
}
53+
};
54+
55+
template <typename B0, typename...>
56+
inline constexpr bool product_is_bounded = bounded_sequence<B0>;
57+
58+
59+
template<typename T, std::size_t RepeatCount>
60+
constexpr auto repeat_tuple(T value) {
61+
return [value]<std::size_t... Is>(std::index_sequence<Is...>) {
62+
return std::tuple{(static_cast<void>(Is), value)...};
63+
}(std::make_index_sequence<RepeatCount>{});
64+
}
65+
66+
template<typename T, std::size_t RepeatCount>
67+
struct TupleRepeated {
68+
69+
using type = decltype(repeat_tuple<T, RepeatCount>(std::declval<T>()));
70+
71+
};
72+
73+
template<typename T, std::size_t RepeatCount>
74+
using tuple_repeated_t = TupleRepeated<T, RepeatCount>::type;
75+
76+
77+
template <std::size_t RepeatCount, typename Base>
78+
struct product_traits_base {
79+
private:
80+
template <typename From, typename To>
81+
using const_like_t = std::conditional_t<std::is_const_v<From>, To const, To>;
82+
83+
template <typename Self>
84+
using cursor_type = tuple_repeated_t<cursor_t<const_like_t<Self, Base>>, RepeatCount>;
85+
86+
87+
template <std::size_t I, typename Self>
88+
static constexpr auto inc_impl(Self& self, cursor_type<Self>& cur) -> cursor_type<Self>&
89+
{
90+
flux::inc(self.base_, std::get<I>(cur));
91+
92+
if constexpr (I > 0) {
93+
if (flux::is_last(self.base_, std::get<I>(cur))) {
94+
std::get<I>(cur) = flux::first(self.base_);
95+
inc_impl<I-1>(self, cur);
96+
}
97+
}
98+
99+
return cur;
100+
}
101+
102+
template <std::size_t I, typename Self>
103+
static constexpr auto dec_impl(Self& self, cursor_type<Self>& cur) -> cursor_type<Self>&
104+
{
105+
if (std::get<I>(cur) == flux::first(self.base_)) {
106+
std::get<I>(cur) = flux::last(self.base_);
107+
if constexpr (I > 0) {
108+
dec_impl<I-1>(self, cur);
109+
}
110+
}
111+
112+
flux::dec(self.base_, std::get<I>(cur));
113+
114+
return cur;
115+
}
116+
117+
template <std::size_t I, typename Self>
118+
static constexpr auto ra_inc_impl(Self& self, cursor_type<Self>& cur, distance_t offset)
119+
-> cursor_type<Self>&
120+
{
121+
if (offset == 0)
122+
return cur;
123+
124+
auto& base = self.base_;
125+
const auto this_index = flux::distance(base, flux::first(base), std::get<I>(cur));
126+
auto new_index = num::checked_add(this_index, offset);
127+
auto this_size = flux::size(base);
128+
129+
// If the new index overflows the maximum or underflows zero, calculate the carryover and fix it.
130+
if (new_index < 0 || new_index >= this_size) {
131+
offset = num::checked_div(new_index, this_size);
132+
new_index = num::checked_mod(new_index, this_size);
133+
134+
// Correct for negative index which may happen when underflowing.
135+
if (new_index < 0) {
136+
new_index = num::checked_add(new_index, this_size);
137+
offset = num::checked_sub(offset, flux::distance_t(1));
138+
}
139+
140+
// Call the next level down if necessary.
141+
if constexpr (I > 0) {
142+
if (offset != 0) {
143+
ra_inc_impl<I-1>(self, cur, offset);
144+
}
145+
}
146+
}
147+
148+
flux::inc(base, std::get<I>(cur), num::checked_sub(new_index, this_index));
149+
150+
return cur;
151+
}
152+
153+
template <std::size_t I, typename Self>
154+
static constexpr auto distance_impl(Self& self,
155+
cursor_type<Self> const& from,
156+
cursor_type<Self> const& to) -> distance_t
157+
{
158+
if constexpr (I == 0) {
159+
return flux::distance(self.base_, std::get<0>(from), std::get<0>(to));
160+
} else {
161+
auto prev_dist = distance_impl<I-1>(self, from, to);
162+
auto our_sz = flux::size(self.base_);
163+
auto our_dist = flux::distance(self.base_, std::get<I>(from), std::get<I>(to));
164+
return prev_dist * our_sz + our_dist;
165+
}
166+
}
167+
168+
public:
169+
170+
template <typename Self>
171+
static constexpr auto first(Self& self) -> cursor_type<Self>
172+
{
173+
return []<std::size_t... Is>(auto&& arg, std::index_sequence<Is...>) {
174+
return cursor_type<Self>((static_cast<void>(Is), flux::first(arg))...);
175+
}(self.base_, std::make_index_sequence<RepeatCount>{});
176+
}
177+
178+
template <typename Self>
179+
static constexpr auto is_last(Self& self, cursor_type<Self> const& cur) -> bool
180+
{
181+
return [&]<std::size_t... N>(std::index_sequence<N...>) {
182+
return (flux::is_last(self.base_, std::get<N>(cur)) || ...);
183+
}(std::make_index_sequence<RepeatCount>{});
184+
}
185+
186+
template <typename Self>
187+
static constexpr auto inc(Self& self, cursor_type<Self>& cur) -> cursor_type<Self>&
188+
{
189+
return inc_impl<RepeatCount - 1>(self, cur);
190+
}
191+
192+
template <typename Self>
193+
requires product_is_bounded<const_like_t<Self, Base>>
194+
static constexpr auto last(Self& self) -> cursor_type<Self>
195+
{
196+
auto cur = first(self);
197+
std::get<0>(cur) = flux::last(self.base_);
198+
return cur;
199+
}
200+
201+
template <typename Self>
202+
requires (bidirectional_sequence<const_like_t<Self, Base>>) &&
203+
(bounded_sequence<const_like_t<Self, Base>>)
204+
static constexpr auto dec(Self& self, cursor_type<Self>& cur) -> cursor_type<Self>&
205+
{
206+
return dec_impl<RepeatCount - 1>(self, cur);
207+
}
208+
209+
template <typename Self>
210+
requires (random_access_sequence<const_like_t<Self, Base>>) &&
211+
(sized_sequence<const_like_t<Self, Base>>)
212+
static constexpr auto inc(Self& self, cursor_type<Self>& cur, distance_t offset)
213+
-> cursor_type<Self>&
214+
{
215+
return ra_inc_impl<RepeatCount - 1>(self, cur, offset);
216+
}
217+
218+
template <typename Self>
219+
requires (random_access_sequence<const_like_t<Self, Base>>) &&
220+
(sized_sequence<const_like_t<Self, Base>>)
221+
static constexpr auto distance(Self& self,
222+
cursor_type<Self> const& from,
223+
cursor_type<Self> const& to) -> distance_t
224+
{
225+
return distance_impl<RepeatCount - 1>(self, from, to);
226+
}
227+
228+
template <typename Self>
229+
requires (sized_sequence<const_like_t<Self, Base>>)
230+
static constexpr auto size(Self& self) -> distance_t
231+
{
232+
auto single_size = flux::size(self.base_);
233+
auto ret = single_size;
234+
for (std::size_t i{1}; i < self.count_; i++) {
235+
ret = ret * single_size;
236+
}
237+
return ret;
238+
}
239+
};
240+
241+
} // end namespace detail
242+
template<typename T>
243+
struct TD;
244+
245+
template <std::size_t RepeatCount, typename Base>
246+
struct sequence_traits<detail::product_adaptor<RepeatCount, Base>>
247+
: detail::product_traits_base<RepeatCount, Base>
248+
{
249+
private:
250+
251+
template <std::size_t I, typename Self, typename Fn>
252+
static constexpr auto read1_(Fn fn, Self& self, cursor_t<Self> const& cur)
253+
-> decltype(auto)
254+
{
255+
return fn(self.base_, std::get<I>(cur));
256+
}
257+
258+
template <typename Fn, typename Self>
259+
static constexpr auto read_(Fn fn, Self& self, cursor_t<Self> const& cur)
260+
{
261+
return [&]<std::size_t... N>(std::index_sequence<N...>) {
262+
return std::tuple<decltype(read1_<N>(fn, self, cur))...>(read1_<N>(fn, self, cur)...);
263+
}(std::make_index_sequence<RepeatCount>{});
264+
}
265+
266+
template <std::size_t I, typename Self, typename Function,
267+
typename... PartialElements>
268+
static constexpr void for_each_while_impl(Self& self,
269+
bool& keep_going,
270+
cursor_t<Self>& cur,
271+
Function&& func,
272+
PartialElements&&... partial_elements)
273+
{
274+
// We need to iterate right to left.
275+
if constexpr (I == RepeatCount - 1) {
276+
std::get<I>(cur) = flux::for_each_while(self.base_,
277+
[&](auto&& elem) {
278+
keep_going = std::invoke(func,
279+
element_t<Self>(FLUX_FWD(partial_elements)..., FLUX_FWD(elem)));
280+
return keep_going;
281+
});
282+
} else {
283+
std::get<I>(cur) = flux::for_each_while(self.base_,
284+
[&](auto&& elem) {
285+
for_each_while_impl<I+1>(
286+
self, keep_going, cur,
287+
func, FLUX_FWD(partial_elements)..., FLUX_FWD(elem));
288+
return keep_going;
289+
});
290+
}
291+
}
292+
293+
294+
public:
295+
using value_type = detail::tuple_repeated_t<value_t<Base>, RepeatCount>;
296+
297+
template <typename Self>
298+
static constexpr auto read_at(Self& self, cursor_t<Self> const& cur)
299+
{
300+
return read_(flux::read_at, self, cur);
301+
}
302+
303+
template <typename Self>
304+
static constexpr auto move_at(Self& self, cursor_t<Self> const& cur)
305+
{
306+
return read_(flux::move_at, self, cur);
307+
}
308+
309+
template <typename Self>
310+
static constexpr auto read_at_unchecked(Self& self, cursor_t<Self> const& cur)
311+
{
312+
return read_(flux::read_at_unchecked, self, cur);
313+
}
314+
315+
template <typename Self>
316+
static constexpr auto move_at_unchecked(Self& self, cursor_t<Self> const& cur)
317+
{
318+
return read_(flux::move_at_unchecked, self, cur);
319+
}
320+
321+
template <typename Self, typename Function>
322+
static constexpr auto for_each_while(Self& self, Function&& func)
323+
-> cursor_t<Self>
324+
{
325+
bool keep_going = true;
326+
cursor_t<Self> cur;
327+
for_each_while_impl<0>(self, keep_going, cur, FLUX_FWD(func));
328+
return cur;
329+
}
330+
};
331+
332+
template<std::size_t RepeatCount>
333+
FLUX_EXPORT inline constexpr auto products = detail::product_fn<RepeatCount>{};
334+
335+
} // end namespace flux
336+
337+
#endif
338+

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ add_executable(test-flux
5252
test_mask.cpp
5353
test_minmax.cpp
5454
test_output_to.cpp
55+
test_products.cpp
5556
test_range_iface.cpp
5657
test_read_only.cpp
5758
test_reverse.cpp

0 commit comments

Comments
 (0)