1 // Copyright 2015-2018 Hans Dembinski
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)
7 #ifndef BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
8 #define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
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>
19 #include <type_traits>
28 Maps at a set of unique values to bin indices.
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.
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.
40 The options `underflow` and `circular` are not allowed. The options `growth`
41 and `overflow` are mutually exclusive.
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>;
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");
61 constexpr category() = default;
62 explicit category(allocator_type alloc) : vec_(alloc) {}
64 /** Construct from iterator range of unique values.
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.
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++);
81 /** Construct axis from iterable sequence of unique values.
83 * \param iterable sequence of unique values.
84 * \param meta description of the axis.
85 * \param alloc allocator instance to use.
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),
92 /** Construct axis from an initializer list of unique values.
94 * \param list `std::initializer_list` of unique values.
95 * \param meta description of the axis.
96 * \param alloc allocator instance to use.
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)) {}
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)));
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);
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,
123 if (idx < 0 || idx >= size())
124 BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
128 /// Return value for index argument.
129 decltype(auto) bin(index_type idx) const noexcept { return value(idx); }
131 /// Returns the number of bins, without over- or underflow.
132 index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
134 /// Returns the options.
135 static constexpr unsigned options() noexcept { return options_type::value; }
137 /// Whether the axis is inclusive (see axis::traits::is_inclusive).
138 static constexpr bool inclusive() noexcept {
139 return options() & (option::overflow | option::growth);
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);
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);
155 auto get_allocator() const { return vec_.get_allocator(); }
157 template <class Archive>
158 void serialize(Archive& ar, unsigned /* version */) {
159 ar& make_nvp("seq", vec_);
160 ar& make_nvp("meta", this->metadata());
166 template <class V, class M, class O, class A>
167 friend class category;
170 #if __cpp_deduction_guides >= 201606
173 category(std::initializer_list<T>)
174 ->category<detail::replace_cstring<std::decay_t<T>>, null_type>;
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>>>;
184 } // namespace histogram