Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / histogram / axis / category.hpp
1 // Copyright 2015-2018 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_AXIS_CATEGORY_HPP
8 #define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
9
10 #include <algorithm>
11 #include <boost/core/nvp.hpp>
12 #include <boost/histogram/axis/iterator.hpp>
13 #include <boost/histogram/axis/metadata_base.hpp>
14 #include <boost/histogram/axis/option.hpp>
15 #include <boost/histogram/fwd.hpp>
16 #include <boost/throw_exception.hpp>
17 #include <stdexcept>
18 #include <string>
19 #include <type_traits>
20 #include <utility>
21 #include <vector>
22
23 namespace boost {
24 namespace histogram {
25 namespace axis {
26
27 /**
28   Maps at a set of unique values to bin indices.
29
30   The axis maps a set of values to bins, following the order of arguments in the
31   constructor. The optional overflow bin for this axis counts input values that
32   are not part of the set. Binning has O(N) complexity, but with a very small
33   factor. For small N (the typical use case) it beats other kinds of lookup.
34
35   @tparam Value input value type, must be equal-comparable.
36   @tparam MetaData type to store meta data.
37   @tparam Options see boost::histogram::axis::option.
38   @tparam Allocator allocator to use for dynamic memory management.
39
40   The options `underflow` and `circular` are not allowed. The options `growth`
41   and `overflow` are mutually exclusive.
42 */
43 template <class Value, class MetaData, class Options, class Allocator>
44 class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
45                  public metadata_base<MetaData> {
46   using value_type = Value;
47   using metadata_type = typename metadata_base<MetaData>::metadata_type;
48   using options_type = detail::replace_default<Options, option::overflow_t>;
49   using allocator_type = Allocator;
50   using vector_type = std::vector<value_type, allocator_type>;
51
52   static_assert(!options_type::test(option::underflow),
53                 "category axis cannot have underflow");
54   static_assert(!options_type::test(option::circular),
55                 "category axis cannot be circular");
56   static_assert(!(options_type::test(option::growth) &&
57                   options_type::test(option::overflow)),
58                 "growing category axis cannot have entries in overflow bin");
59
60 public:
61   constexpr category() = default;
62   explicit category(allocator_type alloc) : vec_(alloc) {}
63
64   /** Construct from iterator range of unique values.
65    *
66    * \param begin     begin of category range of unique values.
67    * \param end       end of category range of unique values.
68    * \param meta      description of the axis.
69    * \param alloc     allocator instance to use.
70    */
71   template <class It, class = detail::requires_iterator<It>>
72   category(It begin, It end, metadata_type meta = {}, allocator_type alloc = {})
73       : metadata_base<MetaData>(std::move(meta)), vec_(alloc) {
74     if (std::distance(begin, end) < 0)
75       BOOST_THROW_EXCEPTION(
76           std::invalid_argument("end must be reachable by incrementing begin"));
77     vec_.reserve(std::distance(begin, end));
78     while (begin != end) vec_.emplace_back(*begin++);
79   }
80
81   /** Construct axis from iterable sequence of unique values.
82    *
83    * \param iterable sequence of unique values.
84    * \param meta     description of the axis.
85    * \param alloc    allocator instance to use.
86    */
87   template <class C, class = detail::requires_iterable<C>>
88   category(const C& iterable, metadata_type meta = {}, allocator_type alloc = {})
89       : category(std::begin(iterable), std::end(iterable), std::move(meta),
90                  std::move(alloc)) {}
91
92   /** Construct axis from an initializer list of unique values.
93    *
94    * \param list   `std::initializer_list` of unique values.
95    * \param meta   description of the axis.
96    * \param alloc  allocator instance to use.
97    */
98   template <class U>
99   category(std::initializer_list<U> list, metadata_type meta = {},
100            allocator_type alloc = {})
101       : category(list.begin(), list.end(), std::move(meta), std::move(alloc)) {}
102
103   /// Return index for value argument.
104   index_type index(const value_type& x) const noexcept {
105     const auto beg = vec_.begin();
106     const auto end = vec_.end();
107     return static_cast<index_type>(std::distance(beg, std::find(beg, end, x)));
108   }
109
110   /// Returns index and shift (if axis has grown) for the passed argument.
111   auto update(const value_type& x) {
112     const auto i = index(x);
113     if (i < size()) return std::make_pair(i, 0);
114     vec_.emplace_back(x);
115     return std::make_pair(i, -1);
116   }
117
118   /// Return value for index argument.
119   /// Throws `std::out_of_range` if the index is out of bounds.
120   auto value(index_type idx) const
121       -> std::conditional_t<std::is_scalar<value_type>::value, value_type,
122                             const value_type&> {
123     if (idx < 0 || idx >= size())
124       BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
125     return vec_[idx];
126   }
127
128   /// Return value for index argument.
129   decltype(auto) bin(index_type idx) const noexcept { return value(idx); }
130
131   /// Returns the number of bins, without over- or underflow.
132   index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
133
134   /// Returns the options.
135   static constexpr unsigned options() noexcept { return options_type::value; }
136
137   /// Whether the axis is inclusive (see axis::traits::is_inclusive).
138   static constexpr bool inclusive() noexcept {
139     return options() & (option::overflow | option::growth);
140   }
141
142   template <class V, class M, class O, class A>
143   bool operator==(const category<V, M, O, A>& o) const noexcept {
144     const auto& a = vec_;
145     const auto& b = o.vec_;
146     return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
147            metadata_base<MetaData>::operator==(o);
148   }
149
150   template <class V, class M, class O, class A>
151   bool operator!=(const category<V, M, O, A>& o) const noexcept {
152     return !operator==(o);
153   }
154
155   auto get_allocator() const { return vec_.get_allocator(); }
156
157   template <class Archive>
158   void serialize(Archive& ar, unsigned /* version */) {
159     ar& make_nvp("seq", vec_);
160     ar& make_nvp("meta", this->metadata());
161   }
162
163 private:
164   vector_type vec_;
165
166   template <class V, class M, class O, class A>
167   friend class category;
168 };
169
170 #if __cpp_deduction_guides >= 201606
171
172 template <class T>
173 category(std::initializer_list<T>)
174     ->category<detail::replace_cstring<std::decay_t<T>>, null_type>;
175
176 template <class T, class M>
177 category(std::initializer_list<T>, M)
178     ->category<detail::replace_cstring<std::decay_t<T>>,
179                detail::replace_cstring<std::decay_t<M>>>;
180
181 #endif
182
183 } // namespace axis
184 } // namespace histogram
185 } // namespace boost
186
187 #endif