BioC++ core-0.7.0
The Modern C++ libraries for Bioinformatics.
 
Loading...
Searching...
No Matches
zip.hpp
Go to the documentation of this file.
1// -----------------------------------------------------------------------------------------------------
2// Copyright (c) 2022 deCODE Genetics
3// Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
4// Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik
5// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
6// shipped with this file and also available at: https://github.com/biocpp/biocpp-core/blob/main/LICENSE.md
7// -----------------------------------------------------------------------------------------------------
8
14#pragma once
15
16#include <algorithm>
17#include <functional>
18#include <ranges>
19
22
24// Contains helpers for bio::views::zip.
25namespace bio::ranges::detail::zip
26{
27
28template <bool is_const, typename t>
30
31// https://eel.is/c++draft/range.zip#concept:zip-is-common
32template <typename... range_ts>
33concept zip_is_common =
34 (sizeof...(range_ts) == 1 && (std::ranges::common_range<range_ts> && ...)) ||
35 (!(std::ranges::bidirectional_range<range_ts> && ...) && (std::ranges::common_range<range_ts> && ...)) ||
36 ((std::ranges::random_access_range<range_ts> && ...) && (std::ranges::sized_range<range_ts> && ...));
37
38// std::abs has problems with ambiguous overloads
39template <typename t>
40constexpr t abs(t && v) noexcept
41{
42 if constexpr (std::is_signed_v<t>)
43 return v < 0 ? -v : v;
44 else
45 return v;
46}
47
48// Returns a new tuple containing the result of applying a function to each tuple element.
49// https://eel.is/c++draft/range.zip.view
50template <typename fun_t, typename tuple_t>
51constexpr auto tuple_transform(fun_t && f, tuple_t && tuple)
52{
53 return std::apply(
54 [&]<typename... ts>(ts &&... elements)
55 { return std::tuple<std::invoke_result_t<fun_t &, ts>...>{std::invoke(f, std::forward<ts>(elements))...}; },
56 std::forward<tuple_t>(tuple));
57}
58
59// Applies a function to each tuple element.
60// https://eel.is/c++draft/range.zip.view
61template <typename fun_t, typename tuple_t>
62constexpr void tuple_for_each(fun_t && f, tuple_t && tuple)
63{
64 std::apply([&]<typename... ts>(ts &&... elements) { (std::invoke(f, std::forward<ts>(elements)), ...); },
65 std::forward<tuple_t>(tuple));
66}
67
68template <bool is_const, typename... range_ts>
69concept all_random_access = (std::ranges::random_access_range<maybe_const<is_const, range_ts>> && ...);
70
71template <bool is_const, typename... range_ts>
72concept all_bidirectional = (std::ranges::bidirectional_range<maybe_const<is_const, range_ts>> && ...);
73
74template <bool is_const, typename... range_ts>
75concept all_forward = (std::ranges::forward_range<maybe_const<is_const, range_ts>> && ...);
76
77// Pre cpp20-iterators: Infer iterator_category from modelled concepts.
78#if defined(__GNUC__) && (__GNUC__ == 10)
79template <bool is_const, typename... range_ts>
80concept all_contiguous = (std::ranges::contiguous_range<maybe_const<is_const, range_ts>> && ...);
81
82template <bool Const, typename... Views>
83struct iterator_category_t
84{
85 using iterator_category = std::conditional_t<
86 all_contiguous<Const, Views...>,
87 std::contiguous_iterator_tag,
89 all_random_access<Const, Views...>,
92 all_bidirectional<Const, Views...>,
95};
96#else // cpp20 iterators
97template <bool>
98struct iterator_category_t;
99template <>
100struct iterator_category_t<true>
101{
102 using iterator_category = std::input_iterator_tag;
103};
104template <>
105struct iterator_category_t<false>
106{};
107#endif
108
109} // namespace bio::ranges::detail::zip
110
111namespace bio::ranges::detail
112{
113
114template <std::ranges::input_range... Views>
115 requires(std::ranges::view<Views> && ...) && (sizeof...(Views) > 0)
116class zip_view : public std::ranges::view_interface<zip_view<Views...>>
117{
118 std::tuple<Views...> views_;
119
120 template <bool>
121 class iterator;
122
123 template <bool>
124 class sentinel;
125
126public:
127 zip_view()
128 requires(std::is_default_constructible_v<Views> && ...)
129 = default;
130 constexpr explicit zip_view(Views... views) : views_(std::move(views)...) {}
131
132 constexpr auto begin()
133 requires(!(simple_view<Views> && ...))
134 {
135 return iterator<false>(zip::tuple_transform(std::ranges::begin, views_));
136 }
137
138 constexpr auto begin() const
139 requires(std::ranges::range<Views const> && ...)
140 {
141 return iterator<true>(zip::tuple_transform(std::ranges::begin, views_));
142 }
143
144 constexpr auto end()
145 requires(!(simple_view<Views> && ...))
146 {
147 if constexpr (!zip::zip_is_common<Views...>)
148 return sentinel<false>(zip::tuple_transform(std::ranges::end, views_));
149 else if constexpr ((std::ranges::random_access_range<Views> && ...))
151 else
152 return iterator<false>(zip::tuple_transform(std::ranges::end, views_));
153 }
154
155 constexpr auto end() const
156 requires(std::ranges::range<Views const> && ...)
157 {
158 if constexpr (!zip::zip_is_common<Views const...>)
159 return sentinel<true>(zip::tuple_transform(std::ranges::end, views_));
160 else if constexpr ((std::ranges::random_access_range<Views const> && ...))
162 else
163 return iterator<true>(zip::tuple_transform(std::ranges::end, views_));
164 }
165
166 constexpr auto size()
167 requires(std::ranges::sized_range<Views> && ...)
168 {
169 return std::apply(
170 [](auto... sizes)
171 {
172 using common_size_t = std::make_unsigned_t<std::common_type_t<decltype(sizes)...>>;
173 return std::ranges::min({static_cast<common_size_t>(sizes)...});
174 },
175 zip::tuple_transform(std::ranges::size, views_));
176 }
177
178 constexpr auto size() const
179 requires(std::ranges::sized_range<Views const> && ...)
180 {
181 return std::apply(
182 [](auto... sizes)
183 {
184 using common_size_t = std::make_unsigned_t<std::common_type_t<decltype(sizes)...>>;
185 return std::ranges::min({static_cast<common_size_t>(sizes)...});
186 },
187 zip::tuple_transform(std::ranges::size, views_));
188 }
189};
190
191template <typename... range_ts>
192zip_view(range_ts &&...) -> zip_view<std::ranges::views::all_t<range_ts>...>;
193
194template <std::ranges::input_range... Views>
195 requires(std::ranges::view<Views> && ...) && (sizeof...(Views) > 0)
196template <bool Const>
197#if defined(__GNUC__) && (__GNUC__ == 10) // cpp17 iterators
198class zip_view<Views...>::iterator : public zip::iterator_category_t<Const, Views...>
199#else // cpp20 iterators
200class zip_view<Views...>::iterator : public zip::iterator_category_t<zip::all_forward<Const, Views...>>
201#endif
202{
203private:
204 constexpr explicit iterator(std::tuple<std::ranges::iterator_t<zip::maybe_const<Const, Views>>...> current) :
205 current_(std::move(current))
206 {}
207
208 friend class zip_view<Views...>;
209
210public:
211 // Exposition-only. Is public for access via friend operator== of the sentinel.
213
214 using iterator_concept = std::conditional_t<
215 zip::all_random_access<Const, Views...>,
218 zip::all_bidirectional<Const, Views...>,
220 std::conditional_t<zip::all_forward<Const, Views...>, std::forward_iterator_tag, std::input_iterator_tag>>>;
223
224 iterator() = default;
225 constexpr iterator(iterator<!Const> i)
226 requires Const && (std::convertible_to<std::ranges::iterator_t<Views>,
227 std::ranges::iterator_t<zip::maybe_const<Const, Views>>> &&
228 ...)
229 : current_(std::move(i.current))
230 {}
231
232 constexpr auto operator*() const
233 {
234 return zip::tuple_transform([](auto & i) -> decltype(auto) { return *i; }, current_);
235 }
236
237 constexpr iterator & operator++()
238 {
239 zip::tuple_for_each([](auto & i) { ++i; }, current_);
240 return *this;
241 }
242
243 constexpr void operator++(int) { ++*this; }
244
245 constexpr iterator operator++(int)
246 requires zip::all_forward<Const, Views...>
247 {
248 auto tmp = *this;
249 ++*this;
250 return tmp;
251 }
252
253 constexpr iterator & operator--()
254 requires zip::all_bidirectional<Const, Views...>
255 {
256 zip::tuple_for_each([](auto & i) { --i; }, current_);
257 return *this;
258 }
259
260 constexpr iterator operator--(int)
261 requires zip::all_bidirectional<Const, Views...>
262 {
263 auto tmp = *this;
264 --*this;
265 return tmp;
266 }
267
268 constexpr iterator & operator+=(difference_type x)
269 requires zip::all_random_access<Const, Views...>
270 {
271 zip::tuple_for_each([&]<typename I>(I & i) { i += std::iter_difference_t<I>(x); }, current_);
272 return *this;
273 }
274
275 constexpr iterator & operator-=(difference_type x)
276 requires zip::all_random_access<Const, Views...>
277 {
278 zip::tuple_for_each([&]<typename I>(I & i) { i -= std::iter_difference_t<I>(x); }, current_);
279 return *this;
280 }
281
282 constexpr auto operator[](difference_type n) const
283 requires zip::all_random_access<Const, Views...>
284 {
285 return zip::tuple_transform([&]<typename I>(I & i) -> decltype(auto)
286 { return i[std::iter_difference_t<I>(n)]; },
287 current_);
288 }
289
290 friend constexpr bool operator==(iterator const & x, iterator const & y)
291 requires(std::equality_comparable<std::ranges::iterator_t<zip::maybe_const<Const, Views>>> && ...)
292 {
293 if constexpr (zip::all_bidirectional<Const, Views...>)
294 {
295 return x.current_ == y.current_;
296 }
297 else
298 {
299 return [&]<size_t... N>(std::integer_sequence<size_t, N...>) {
300 return ((std::get<N>(x.current_) == std::get<N>(y.current_)) || ...);
301 }(std::index_sequence_for<Views...>{});
302 }
303 }
304
305 friend constexpr auto operator<=>(iterator const & x, iterator const & y)
306 requires zip::all_random_access<Const, Views...> &&
307 (std::three_way_comparable<std::ranges::iterator_t<zip::maybe_const<Const, Views>>> && ...)
308 {
309 return x.current_ <=> y.current_;
310 }
311
312 friend constexpr iterator operator+(iterator const & i, difference_type n)
313 requires zip::all_random_access<Const, Views...>
314 {
315 auto r = i;
316 r += n;
317 return r;
318 }
319
320 friend constexpr iterator operator+(difference_type n, iterator const & i)
321 requires zip::all_random_access<Const, Views...>
322 {
323 return i + n;
324 }
325
326 friend constexpr iterator operator-(iterator const & i, difference_type n)
327 requires zip::all_random_access<Const, Views...>
328 {
329 auto r = i;
330 r -= n;
331 return r;
332 }
333
334 friend constexpr difference_type operator-(iterator const & x, iterator const & y)
335 requires(std::sized_sentinel_for<std::ranges::iterator_t<zip::maybe_const<Const, Views>>,
336 std::ranges::iterator_t<zip::maybe_const<Const, Views>>> &&
337 ...)
338 {
339 return [&]<size_t... N>(std::integer_sequence<size_t, N...>)
340 {
341 return std::ranges::min(
342 {static_cast<difference_type>(std::get<N>(x.current_) - std::get<N>(y.current_))...},
343 [](difference_type a, difference_type b) { return zip::abs(b) < zip::abs(a); });
344 }(std::index_sequence_for<Views...>{});
345 }
346
347 friend constexpr auto iter_move(iterator const & i) noexcept(
348 (noexcept(
349 std::ranges::iter_move(std::declval<std::ranges::iterator_t<zip::maybe_const<Const, Views>> const &>())) &&
350 ...) &&
351 (std::is_nothrow_move_constructible_v<std::ranges::range_rvalue_reference_t<zip::maybe_const<Const, Views>>> &&
352 ...))
353 {
354 return zip::tuple_transform(std::ranges::iter_move, i.current_);
355 }
356
357 friend constexpr void iter_swap(iterator const & l, iterator const & r) noexcept(
358 (noexcept(
359 std::ranges::iter_swap(std::declval<std::ranges::iterator_t<zip::maybe_const<Const, Views>> const &>(),
360 std::declval<std::ranges::iterator_t<zip::maybe_const<Const, Views>> const &>())) &&
361 ...))
362 requires(std::indirectly_swappable<std::ranges::iterator_t<zip::maybe_const<Const, Views>>> && ...)
363 {
364 [&]<size_t... N>(std::integer_sequence<size_t, N...>) {
365 (std::ranges::iter_swap(std::get<N>(l.current_), std::get<N>(r.current)), ...);
366 }(std::index_sequence_for<Views...>{});
367 }
368};
369
370template <std::ranges::input_range... Views>
371 requires(std::ranges::view<Views> && ...) && (sizeof...(Views) > 0)
372template <bool Const>
373class zip_view<Views...>::sentinel
374{
375private:
376 constexpr explicit sentinel(std::tuple<std::ranges::sentinel_t<zip::maybe_const<Const, Views>>...> end) :
377 end_(std::move(end))
378 {}
379
380 friend class zip_view<Views...>;
381
382public:
383 // Exposition-only. Is public such that it can be accessed by friends.
385
386 sentinel() = default;
387 constexpr sentinel(sentinel<!Const> i)
388 requires Const && (std::convertible_to<std::ranges::sentinel_t<Views>,
389 std::ranges::sentinel_t<zip::maybe_const<Const, Views>>> &&
390 ...)
391 : end_(std::move(i.end_))
392 {}
393
394 template <bool OtherConst>
395 requires(std::sentinel_for<std::ranges::sentinel_t<zip::maybe_const<Const, Views>>,
396 std::ranges::iterator_t<zip::maybe_const<OtherConst, Views>>> &&
397 ...)
398 friend constexpr bool operator==(iterator<OtherConst> const & x, sentinel const & y)
399 {
400 return [&]<size_t... N>(std::integer_sequence<size_t, N...>)
401 { return ((std::get<N>(x.current_) == std::get<N>(y.end_)) || ...); }(std::index_sequence_for<Views...>{});
402 }
403
404 template <bool OtherConst>
405 requires(std::sized_sentinel_for<std::ranges::sentinel_t<zip::maybe_const<Const, Views>>,
406 std::ranges::iterator_t<zip::maybe_const<OtherConst, Views>>> &&
407 ...)
408 friend constexpr std::common_type_t<std::ranges::range_difference_t<zip::maybe_const<OtherConst, Views>>...>
409 operator-(iterator<OtherConst> const & x, sentinel const & y)
410 {
412 return [&]<size_t... N>(std::integer_sequence<size_t, N...>)
413 {
414 return std::ranges::min({static_cast<return_t>(std::get<N>(x.current_) - std::get<N>(y.end_))...},
415 [](return_t a, return_t b) { return zip::abs(b) < zip::abs(a); });
416 }(std::index_sequence_for<Views...>{});
417 }
418
419 template <bool OtherConst>
420 requires(std::sized_sentinel_for<std::ranges::sentinel_t<zip::maybe_const<Const, Views>>,
421 std::ranges::iterator_t<zip::maybe_const<OtherConst, Views>>> &&
422 ...)
423 friend constexpr std::common_type_t<std::ranges::range_difference_t<zip::maybe_const<OtherConst, Views>>...>
424 operator-(sentinel const & y, iterator<OtherConst> const & x)
425 {
426 return -(x - y);
427 }
428};
429
430struct zip_fn
431{
432 constexpr auto operator()() const { return std::views::empty<std::tuple<>>; }
433
434 template <typename... urng_ts>
435 requires(sizeof...(urng_ts) >= 1)
436 constexpr auto operator()(urng_ts &&... ranges) const
437 {
438 return zip_view{std::views::all(std::forward<urng_ts>(ranges))...};
439 }
440};
441
442} // namespace bio::ranges::detail
444
445namespace bio::ranges::views
446{
447
455inline constexpr auto zip = detail::zip_fn{};
456
457} // namespace bio::ranges::views
T apply(T... args)
T begin(T... args)
T declval(T... args)
T end(T... args)
constexpr auto size
A type trait that holds the size of a (semi-)alphabet.
Definition: concept.hpp:517
T invoke(T... args)
T iter_swap(T... args)
T min(T... args)
T move(T... args)
The BioC++ namespace for views.
constexpr auto zip
A view adaptor that produces a tuple-like value of all passed views.
Definition: zip.hpp:455
Additional non-standard concepts for ranges.
Auxiliary header for the views submodule .