]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | // Copyright 2015-2016 Hans Dembinski |
2 | // | |
3 | // Distributed under the Boost Software License, Version 1.0. | |
4 | // (See accompanying file LICENSE_1_0.txt | |
5 | // or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | ||
7 | #ifndef BOOST_HISTOGRAM_INDEXED_HPP | |
8 | #define BOOST_HISTOGRAM_INDEXED_HPP | |
9 | ||
10 | #include <array> | |
11 | #include <boost/config.hpp> | |
12 | #include <boost/histogram/axis/traits.hpp> | |
13 | #include <boost/histogram/detail/axes.hpp> | |
14 | #include <boost/histogram/detail/iterator_adaptor.hpp> | |
15 | #include <boost/histogram/detail/operators.hpp> | |
16 | #include <boost/histogram/fwd.hpp> | |
17 | #include <iterator> | |
18 | #include <type_traits> | |
19 | #include <utility> | |
20 | ||
21 | namespace boost { | |
22 | namespace histogram { | |
23 | ||
24 | /** Coverage mode of the indexed range generator. | |
25 | ||
26 | Defines options for the iteration strategy. | |
27 | */ | |
28 | enum class coverage { | |
29 | inner, /*!< iterate over inner bins, exclude underflow and overflow */ | |
30 | all, /*!< iterate over all bins, including underflow and overflow */ | |
31 | }; | |
32 | ||
33 | /** Input iterator range over histogram bins with multi-dimensional index. | |
34 | ||
35 | The iterator returned by begin() can only be incremented. begin() may only be called | |
36 | once, calling it a second time returns the end() iterator. If several copies of the | |
37 | input iterators exist, the other copies become invalid if one of them is incremented. | |
38 | */ | |
39 | template <class Histogram> | |
40 | class BOOST_ATTRIBUTE_NODISCARD indexed_range { | |
41 | private: | |
42 | using histogram_type = Histogram; | |
43 | static constexpr std::size_t buffer_size = | |
44 | detail::buffer_size<typename std::remove_const_t<histogram_type>::axes_type>::value; | |
45 | ||
46 | public: | |
47 | using value_iterator = std::conditional_t<std::is_const<histogram_type>::value, | |
48 | typename histogram_type::const_iterator, | |
49 | typename histogram_type::iterator>; | |
50 | using value_reference = typename std::iterator_traits<value_iterator>::reference; | |
51 | using value_type = typename std::iterator_traits<value_iterator>::value_type; | |
52 | ||
53 | class iterator; | |
f67539c2 | 54 | using range_iterator [[deprecated("use iterator instead")]] = iterator; ///< deprecated |
92f5a8d4 TL |
55 | |
56 | /** Lightweight view to access value and index of current cell. | |
57 | ||
58 | The methods provide access to the current cell indices and bins. It acts like a | |
59 | pointer to the cell value, and in a limited way also like a reference. To interoperate | |
60 | with the algorithms of the standard library, the accessor is implicitly convertible to | |
61 | a cell value. Assignments and comparisons are passed through to the cell. An accessor | |
62 | is coupled to its parent indexed_range::iterator. Moving the parent iterator | |
63 | forward also updates the linked accessor. Accessors are not copyable. They cannot be | |
64 | stored in containers, but indexed_range::iterator can be stored. | |
65 | */ | |
66 | class BOOST_ATTRIBUTE_NODISCARD accessor : detail::mirrored<accessor, void> { | |
67 | public: | |
68 | /// Array-like view into the current multi-dimensional index. | |
69 | class index_view { | |
70 | using index_pointer = const typename iterator::index_data*; | |
71 | ||
72 | public: | |
73 | using const_reference = const axis::index_type&; | |
f67539c2 TL |
74 | using reference [[deprecated("use const_reference instead")]] = |
75 | const_reference; ///< deprecated | |
92f5a8d4 TL |
76 | |
77 | /// implementation detail | |
78 | class const_iterator | |
79 | : public detail::iterator_adaptor<const_iterator, index_pointer, | |
80 | const_reference> { | |
81 | public: | |
82 | const_reference operator*() const noexcept { return const_iterator::base()->idx; } | |
83 | ||
84 | private: | |
85 | explicit const_iterator(index_pointer i) noexcept | |
86 | : const_iterator::iterator_adaptor_(i) {} | |
87 | ||
88 | friend class index_view; | |
89 | }; | |
90 | ||
91 | const_iterator begin() const noexcept { return const_iterator{begin_}; } | |
92 | const_iterator end() const noexcept { return const_iterator{end_}; } | |
93 | std::size_t size() const noexcept { | |
94 | return static_cast<std::size_t>(end_ - begin_); | |
95 | } | |
96 | const_reference operator[](unsigned d) const noexcept { return begin_[d].idx; } | |
97 | const_reference at(unsigned d) const { return begin_[d].idx; } | |
98 | ||
99 | private: | |
100 | /// implementation detail | |
101 | index_view(index_pointer b, index_pointer e) : begin_(b), end_(e) {} | |
102 | ||
103 | index_pointer begin_, end_; | |
104 | friend class accessor; | |
105 | }; | |
106 | ||
107 | // assignment is pass-through | |
108 | accessor& operator=(const accessor& o) { | |
109 | get() = o.get(); | |
110 | return *this; | |
111 | } | |
112 | ||
113 | // assignment is pass-through | |
114 | template <class T> | |
115 | accessor& operator=(const T& x) { | |
116 | get() = x; | |
117 | return *this; | |
118 | } | |
119 | ||
120 | /// Returns the cell reference. | |
121 | value_reference get() const noexcept { return *(iter_.iter_); } | |
122 | /// @copydoc get() | |
123 | value_reference operator*() const noexcept { return get(); } | |
124 | /// Access fields and methods of the cell object. | |
125 | value_iterator operator->() const noexcept { return iter_.iter_; } | |
126 | ||
127 | /// Access current index. | |
128 | /// @param d axis dimension. | |
129 | axis::index_type index(unsigned d = 0) const noexcept { | |
130 | return iter_.indices_[d].idx; | |
131 | } | |
132 | ||
133 | /// Access indices as an iterable range. | |
134 | index_view indices() const noexcept { | |
135 | BOOST_ASSERT(iter_.indices_.hist_); | |
f67539c2 | 136 | return {iter_.indices_.begin(), iter_.indices_.end()}; |
92f5a8d4 TL |
137 | } |
138 | ||
139 | /// Access current bin. | |
140 | /// @tparam N axis dimension. | |
141 | template <unsigned N = 0> | |
142 | decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const { | |
143 | BOOST_ASSERT(iter_.indices_.hist_); | |
144 | return iter_.indices_.hist_->axis(std::integral_constant<unsigned, N>()) | |
145 | .bin(index(N)); | |
146 | } | |
147 | ||
148 | /// Access current bin. | |
149 | /// @param d axis dimension. | |
150 | decltype(auto) bin(unsigned d) const { | |
151 | BOOST_ASSERT(iter_.indices_.hist_); | |
152 | return iter_.indices_.hist_->axis(d).bin(index(d)); | |
153 | } | |
154 | ||
155 | /** Computes density in current cell. | |
156 | ||
157 | The density is computed as the cell value divided by the product of bin widths. Axes | |
158 | without bin widths, like axis::category, are treated as having unit bin with. | |
159 | */ | |
160 | double density() const { | |
161 | BOOST_ASSERT(iter_.indices_.hist_); | |
162 | double x = 1; | |
163 | unsigned d = 0; | |
164 | iter_.indices_.hist_->for_each_axis([&](const auto& a) { | |
165 | const auto w = axis::traits::width_as<double>(a, this->index(d++)); | |
166 | x *= w ? w : 1; | |
167 | }); | |
168 | return get() / x; | |
169 | } | |
170 | ||
171 | // forward all comparison operators to the value | |
172 | bool operator<(const accessor& o) noexcept { return get() < o.get(); } | |
173 | bool operator>(const accessor& o) noexcept { return get() > o.get(); } | |
174 | bool operator==(const accessor& o) noexcept { return get() == o.get(); } | |
175 | bool operator!=(const accessor& o) noexcept { return get() != o.get(); } | |
176 | bool operator<=(const accessor& o) noexcept { return get() <= o.get(); } | |
177 | bool operator>=(const accessor& o) noexcept { return get() >= o.get(); } | |
178 | ||
179 | template <class U> | |
180 | bool operator<(const U& o) const noexcept { | |
181 | return get() < o; | |
182 | } | |
183 | ||
184 | template <class U> | |
185 | bool operator>(const U& o) const noexcept { | |
186 | return get() > o; | |
187 | } | |
188 | ||
189 | template <class U> | |
190 | bool operator==(const U& o) const noexcept { | |
191 | return get() == o; | |
192 | } | |
193 | ||
194 | template <class U> | |
195 | bool operator!=(const U& o) const noexcept { | |
196 | return get() != o; | |
197 | } | |
198 | ||
199 | template <class U> | |
200 | bool operator<=(const U& o) const noexcept { | |
201 | return get() <= o; | |
202 | } | |
203 | ||
204 | template <class U> | |
205 | bool operator>=(const U& o) const noexcept { | |
206 | return get() >= o; | |
207 | } | |
208 | ||
209 | operator value_type() const noexcept { return get(); } | |
210 | ||
211 | private: | |
212 | accessor(iterator& i) noexcept : iter_(i) {} | |
213 | ||
214 | accessor(const accessor&) = default; // only callable by indexed_range::iterator | |
215 | ||
216 | iterator& iter_; | |
217 | ||
218 | friend class iterator; | |
219 | }; | |
220 | ||
221 | /// implementation detail | |
222 | class iterator { | |
223 | public: | |
224 | using value_type = typename indexed_range::value_type; | |
225 | using reference = accessor; | |
226 | ||
227 | private: | |
228 | struct pointer_proxy { | |
229 | reference* operator->() noexcept { return std::addressof(ref_); } | |
230 | reference ref_; | |
231 | }; | |
232 | ||
233 | public: | |
234 | using pointer = pointer_proxy; | |
235 | using difference_type = std::ptrdiff_t; | |
236 | using iterator_category = std::forward_iterator_tag; | |
237 | ||
238 | reference operator*() noexcept { return *this; } | |
239 | pointer operator->() noexcept { return pointer_proxy{operator*()}; } | |
240 | ||
241 | iterator& operator++() { | |
f67539c2 TL |
242 | BOOST_ASSERT(iter_ < indices_.hist_->end()); |
243 | const auto cbeg = indices_.begin(); | |
244 | auto c = cbeg; | |
92f5a8d4 | 245 | ++iter_; |
f67539c2 TL |
246 | ++c->idx; |
247 | if (c->idx < c->end) return *this; | |
248 | while (c->idx == c->end) { | |
249 | iter_ += c->end_skip; | |
250 | if (++c == indices_.end()) return *this; | |
92f5a8d4 | 251 | ++c->idx; |
f67539c2 TL |
252 | } |
253 | while (c-- != cbeg) { | |
254 | c->idx = c->begin; | |
255 | iter_ += c->begin_skip; | |
92f5a8d4 TL |
256 | } |
257 | return *this; | |
258 | } | |
259 | ||
260 | iterator operator++(int) { | |
261 | auto prev = *this; | |
262 | operator++(); | |
263 | return prev; | |
264 | } | |
265 | ||
266 | bool operator==(const iterator& x) const noexcept { return iter_ == x.iter_; } | |
267 | bool operator!=(const iterator& x) const noexcept { return !operator==(x); } | |
268 | ||
f67539c2 TL |
269 | // make iterator ready for C++17 sentinels |
270 | bool operator==(const value_iterator& x) const noexcept { return iter_ == x; } | |
271 | bool operator!=(const value_iterator& x) const noexcept { return !operator==(x); } | |
272 | ||
273 | // useful for iterator debugging | |
274 | std::size_t offset() const noexcept { return iter_ - indices_.hist_->begin(); } | |
275 | ||
92f5a8d4 | 276 | private: |
f67539c2 | 277 | iterator(value_iterator i, histogram_type& h) : iter_(i), indices_(&h) {} |
92f5a8d4 TL |
278 | |
279 | value_iterator iter_; | |
280 | ||
281 | struct index_data { | |
f67539c2 TL |
282 | axis::index_type idx, begin, end; |
283 | std::size_t begin_skip, end_skip; | |
92f5a8d4 TL |
284 | }; |
285 | ||
f67539c2 | 286 | struct indices_t : private std::array<index_data, buffer_size> { |
92f5a8d4 | 287 | using base_type = std::array<index_data, buffer_size>; |
f67539c2 TL |
288 | using pointer = index_data*; |
289 | using const_pointer = const index_data*; | |
92f5a8d4 TL |
290 | |
291 | indices_t(histogram_type* h) noexcept : hist_{h} {} | |
292 | ||
f67539c2 TL |
293 | using base_type::operator[]; |
294 | unsigned size() const noexcept { return hist_->rank(); } | |
295 | pointer begin() noexcept { return base_type::data(); } | |
296 | const_pointer begin() const noexcept { return base_type::data(); } | |
297 | pointer end() noexcept { return begin() + size(); } | |
298 | const_pointer end() const noexcept { return begin() + size(); } | |
299 | ||
92f5a8d4 TL |
300 | histogram_type* hist_; |
301 | } indices_; | |
302 | ||
303 | friend class indexed_range; | |
304 | }; | |
305 | ||
f67539c2 TL |
306 | indexed_range(histogram_type& hist, coverage cov) |
307 | : begin_(hist.begin(), hist), end_(hist.end(), hist) { | |
308 | begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov, | |
309 | stride = std::size_t{1}, | |
310 | this](const auto& a) mutable { | |
311 | using opt = axis::traits::get_options<std::decay_t<decltype(a)>>; | |
312 | constexpr axis::index_type under = opt::test(axis::option::underflow); | |
313 | constexpr axis::index_type over = opt::test(axis::option::overflow); | |
314 | const auto size = a.size(); | |
315 | ||
316 | // -1 if underflow and cover all, else 0 | |
317 | ca->begin = cov == coverage::all ? -under : 0; | |
318 | // size + 1 if overflow and cover all, else size | |
319 | ca->end = cov == coverage::all ? size + over : size; | |
320 | ca->idx = ca->begin; | |
321 | ||
322 | // if axis has *flow and coverage::all OR axis has no *flow: | |
323 | // begin + under == 0, size + over - end == 0 | |
324 | // if axis has *flow and coverage::inner: | |
325 | // begin + under == 1, size + over - end == 1 | |
326 | ca->begin_skip = static_cast<std::size_t>(ca->begin + under) * stride; | |
327 | ca->end_skip = static_cast<std::size_t>(size + over - ca->end) * stride; | |
328 | begin_.iter_ += ca->begin_skip; | |
329 | ||
330 | stride *= size + under + over; | |
331 | ++ca; | |
332 | }); | |
92f5a8d4 TL |
333 | } |
334 | ||
335 | iterator begin() noexcept { return begin_; } | |
336 | iterator end() noexcept { return end_; } | |
337 | ||
338 | private: | |
339 | iterator begin_, end_; | |
340 | }; | |
341 | ||
342 | /** Generates an indexed range of <a | |
343 | href="https://en.cppreference.com/w/cpp/named_req/ForwardIterator">forward iterators</a> | |
344 | over the histogram cells. | |
345 | ||
346 | Use this in a range-based for loop: | |
347 | ||
348 | ``` | |
349 | for (auto&& x : indexed(hist)) { ... } | |
350 | ``` | |
351 | ||
352 | This generates an optimized loop which is nearly always faster than a hand-written loop | |
353 | over the histogram cells. The iterators dereference to an indexed_range::accessor, which | |
354 | has methods to query the current indices and bins and acts like a pointer to the cell | |
355 | value. The returned iterators are forward iterators. They can be stored in a container, | |
356 | but may not be used after the life-time of the histogram ends. | |
357 | ||
358 | @returns indexed_range | |
359 | ||
360 | @param hist Reference to the histogram. | |
361 | @param cov Iterate over all or only inner bins (optional, default: inner). | |
362 | */ | |
363 | template <class Histogram> | |
364 | auto indexed(Histogram&& hist, coverage cov = coverage::inner) { | |
365 | return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist), | |
366 | cov}; | |
367 | } | |
368 | ||
369 | } // namespace histogram | |
370 | } // namespace boost | |
371 | ||
372 | #endif |