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