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> // BOOST_ATTRIBUTE_NODISCARD
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 unsigned buffer_size =
44 detail::buffer_size<typename std::decay_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;
54 using range_iterator [[deprecated("use iterator instead")]] = iterator; ///< deprecated
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&;
74 using reference [[deprecated("use const_reference instead")]] =
75 const_reference; ///< deprecated
76
77 /// implementation detail
78 class const_iterator
79 : public detail::iterator_adaptor<const_iterator, index_pointer,
80 const_reference> {
81 public:
operator *() const82 const_reference operator*() const noexcept { return const_iterator::base()->idx; }
83
84 private:
const_iterator(index_pointer i)85 explicit const_iterator(index_pointer i) noexcept
86 : const_iterator::iterator_adaptor_(i) {}
87
88 friend class index_view;
89 };
90
begin() const91 const_iterator begin() const noexcept { return const_iterator{begin_}; }
end() const92 const_iterator end() const noexcept { return const_iterator{end_}; }
size() const93 std::size_t size() const noexcept {
94 return static_cast<std::size_t>(end_ - begin_);
95 }
operator [](unsigned d) const96 const_reference operator[](unsigned d) const noexcept { return begin_[d].idx; }
at(unsigned d) const97 const_reference at(unsigned d) const { return begin_[d].idx; }
98
99 private:
100 /// implementation detail
index_view(index_pointer b,index_pointer e)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
operator =(const accessor & o)108 accessor& operator=(const accessor& o) {
109 get() = o.get();
110 return *this;
111 }
112
113 // assignment is pass-through
114 template <class T>
operator =(const T & x)115 accessor& operator=(const T& x) {
116 get() = x;
117 return *this;
118 }
119
120 /// Returns the cell reference.
get() const121 value_reference get() const noexcept { return *(iter_.iter_); }
122 /// @copydoc get()
operator *() const123 value_reference operator*() const noexcept { return get(); }
124 /// Access fields and methods of the cell object.
operator ->() const125 value_iterator operator->() const noexcept { return iter_.iter_; }
126
127 /// Access current index.
128 /// @param d axis dimension.
index(unsigned d=0) const129 axis::index_type index(unsigned d = 0) const noexcept {
130 return iter_.indices_[d].idx;
131 }
132
133 /// Access indices as an iterable range.
indices() const134 index_view indices() const noexcept {
135 assert(iter_.indices_.hist_);
136 return {iter_.indices_.begin(), iter_.indices_.end()};
137 }
138
139 /// Access current bin.
140 /// @tparam N axis dimension.
141 template <unsigned N = 0>
bin(std::integral_constant<unsigned,N>={}) const142 decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const {
143 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.
bin(unsigned d) const150 decltype(auto) bin(unsigned d) const {
151 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 */
density() const160 double density() const {
161 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 != 0 ? w : 1;
167 });
168 return get() / x;
169 }
170
171 // forward all comparison operators to the value
operator <(const accessor & o)172 bool operator<(const accessor& o) noexcept { return get() < o.get(); }
operator >(const accessor & o)173 bool operator>(const accessor& o) noexcept { return get() > o.get(); }
operator ==(const accessor & o)174 bool operator==(const accessor& o) noexcept { return get() == o.get(); }
operator !=(const accessor & o)175 bool operator!=(const accessor& o) noexcept { return get() != o.get(); }
operator <=(const accessor & o)176 bool operator<=(const accessor& o) noexcept { return get() <= o.get(); }
operator >=(const accessor & o)177 bool operator>=(const accessor& o) noexcept { return get() >= o.get(); }
178
179 template <class U>
operator <(const U & o) const180 bool operator<(const U& o) const noexcept {
181 return get() < o;
182 }
183
184 template <class U>
operator >(const U & o) const185 bool operator>(const U& o) const noexcept {
186 return get() > o;
187 }
188
189 template <class U>
operator ==(const U & o) const190 bool operator==(const U& o) const noexcept {
191 return get() == o;
192 }
193
194 template <class U>
operator !=(const U & o) const195 bool operator!=(const U& o) const noexcept {
196 return get() != o;
197 }
198
199 template <class U>
operator <=(const U & o) const200 bool operator<=(const U& o) const noexcept {
201 return get() <= o;
202 }
203
204 template <class U>
operator >=(const U & o) const205 bool operator>=(const U& o) const noexcept {
206 return get() >= o;
207 }
208
operator value_type() const209 operator value_type() const noexcept { return get(); }
210
211 private:
accessor(iterator & i)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 {
operator ->boost::histogram::indexed_range::iterator::pointer_proxy229 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
operator *()238 reference operator*() noexcept { return *this; }
operator ->()239 pointer operator->() noexcept { return pointer_proxy{operator*()}; }
240
operator ++()241 iterator& operator++() {
242 assert(iter_ < indices_.hist_->end());
243 const auto cbeg = indices_.begin();
244 auto c = cbeg;
245 ++iter_;
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;
251 ++c->idx;
252 }
253 while (c-- != cbeg) {
254 c->idx = c->begin;
255 iter_ += c->begin_skip;
256 }
257 return *this;
258 }
259
operator ++(int)260 iterator operator++(int) {
261 auto prev = *this;
262 operator++();
263 return prev;
264 }
265
operator ==(const iterator & x) const266 bool operator==(const iterator& x) const noexcept { return iter_ == x.iter_; }
operator !=(const iterator & x) const267 bool operator!=(const iterator& x) const noexcept { return !operator==(x); }
268
269 // make iterator ready for C++17 sentinels
operator ==(const value_iterator & x) const270 bool operator==(const value_iterator& x) const noexcept { return iter_ == x; }
operator !=(const value_iterator & x) const271 bool operator!=(const value_iterator& x) const noexcept { return !operator==(x); }
272
273 // useful for iterator debugging
offset() const274 std::size_t offset() const noexcept { return iter_ - indices_.hist_->begin(); }
275
276 private:
iterator(value_iterator i,histogram_type & h)277 iterator(value_iterator i, histogram_type& h) : iter_(i), indices_(&h) {}
278
279 value_iterator iter_;
280
281 struct index_data {
282 axis::index_type idx, begin, end;
283 std::size_t begin_skip, end_skip;
284 };
285
286 struct indices_t : private std::array<index_data, buffer_size> {
287 using base_type = std::array<index_data, buffer_size>;
288 using pointer = index_data*;
289 using const_pointer = const index_data*;
290
indices_tboost::histogram::indexed_range::iterator::indices_t291 indices_t(histogram_type* h) noexcept : hist_{h} {}
292
293 using base_type::operator[];
sizeboost::histogram::indexed_range::iterator::indices_t294 unsigned size() const noexcept { return hist_->rank(); }
beginboost::histogram::indexed_range::iterator::indices_t295 pointer begin() noexcept { return base_type::data(); }
beginboost::histogram::indexed_range::iterator::indices_t296 const_pointer begin() const noexcept { return base_type::data(); }
endboost::histogram::indexed_range::iterator::indices_t297 pointer end() noexcept { return begin() + size(); }
endboost::histogram::indexed_range::iterator::indices_t298 const_pointer end() const noexcept { return begin() + size(); }
299
300 histogram_type* hist_;
301 } indices_;
302
303 friend class indexed_range;
304 };
305
indexed_range(histogram_type & hist,coverage cov)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 });
333 }
334
begin()335 iterator begin() noexcept { return begin_; }
end()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>
indexed(Histogram && hist,coverage cov=coverage::inner)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
373