123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- // Copyright 2015-2018 Hans Dembinski
- //
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE_1_0.txt
- // or copy at http://www.boost.org/LICENSE_1_0.txt)
- #ifndef BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
- #define BOOST_HISTOGRAM_AXIS_CATEGORY_HPP
- #include <algorithm>
- #include <boost/core/nvp.hpp>
- #include <boost/histogram/axis/iterator.hpp>
- #include <boost/histogram/axis/metadata_base.hpp>
- #include <boost/histogram/axis/option.hpp>
- #include <boost/histogram/detail/detect.hpp>
- #include <boost/histogram/detail/relaxed_equal.hpp>
- #include <boost/histogram/fwd.hpp>
- #include <boost/throw_exception.hpp>
- #include <stdexcept>
- #include <string>
- #include <type_traits>
- #include <utility>
- #include <vector>
- namespace boost {
- namespace histogram {
- namespace axis {
- /** Maps at a set of unique values to bin indices.
- The axis maps a set of values to bins, following the order of arguments in the
- constructor. The optional overflow bin for this axis counts input values that
- are not part of the set. Binning has O(N) complexity, but with a very small
- factor. For small N (the typical use case) it beats other kinds of lookup.
- @tparam Value input value type, must be equal-comparable.
- @tparam MetaData type to store meta data.
- @tparam Options see boost::histogram::axis::option.
- @tparam Allocator allocator to use for dynamic memory management.
- The options `underflow` and `circular` are not allowed. The options `growth`
- and `overflow` are mutually exclusive.
- */
- template <class Value, class MetaData, class Options, class Allocator>
- class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
- public metadata_base_t<MetaData> {
- // these must be private, so that they are not automatically inherited
- using value_type = Value;
- using metadata_base = metadata_base_t<MetaData>;
- using metadata_type = typename metadata_base::metadata_type;
- using options_type = detail::replace_default<Options, option::overflow_t>;
- using allocator_type = Allocator;
- using vector_type = std::vector<value_type, allocator_type>;
- public:
- constexpr category() = default;
- explicit category(allocator_type alloc) : vec_(alloc) {}
- /** Construct from forward iterator range of unique values.
- @param begin begin of category range of unique values.
- @param end end of category range of unique values.
- @param meta description of the axis (optional).
- @param options see boost::histogram::axis::option (optional).
- @param alloc allocator instance to use (optional).
- The constructor throws `std::invalid_argument` if iterator range is invalid. If the
- range contains duplicated values, the behavior of the axis is undefined.
- The arguments meta and alloc are passed by value. If you move either of them into the
- axis and the constructor throws, their values are lost. Do not move if you cannot
- guarantee that the bin description is not valid.
- */
- template <class It, class = detail::requires_iterator<It>>
- category(It begin, It end, metadata_type meta = {}, options_type options = {},
- allocator_type alloc = {})
- : metadata_base(std::move(meta)), vec_(alloc) {
- // static_asserts were moved here from class scope to satisfy deduction in gcc>=11
- static_assert(!options.test(option::underflow),
- "category axis cannot have underflow");
- static_assert(!options.test(option::circular), "category axis cannot be circular");
- static_assert(!(options.test(option::growth) && options.test(option::overflow)),
- "growing category axis cannot have entries in overflow bin");
- if (std::distance(begin, end) < 0)
- BOOST_THROW_EXCEPTION(
- std::invalid_argument("end must be reachable by incrementing begin"));
- vec_.reserve(std::distance(begin, end));
- while (begin != end) vec_.emplace_back(*begin++);
- }
- // kept for backward compatibility; requires_allocator is a workaround for deduction
- // guides in gcc>=11
- template <class It, class A, class = detail::requires_iterator<It>,
- class = detail::requires_allocator<A>>
- category(It begin, It end, metadata_type meta, A alloc)
- : category(begin, end, std::move(meta), {}, std::move(alloc)) {}
- /** Construct axis from iterable sequence of unique values.
- @param iterable sequence of unique values.
- @param meta description of the axis.
- @param options see boost::histogram::axis::option (optional).
- @param alloc allocator instance to use.
- */
- template <class C, class = detail::requires_iterable<C>>
- category(const C& iterable, metadata_type meta = {}, options_type options = {},
- allocator_type alloc = {})
- : category(std::begin(iterable), std::end(iterable), std::move(meta), options,
- std::move(alloc)) {}
- // kept for backward compatibility; requires_allocator is a workaround for deduction
- // guides in gcc>=11
- template <class C, class A, class = detail::requires_iterable<C>,
- class = detail::requires_allocator<A>>
- category(const C& iterable, metadata_type meta, A alloc)
- : category(std::begin(iterable), std::end(iterable), std::move(meta), {},
- std::move(alloc)) {}
- /** Construct axis from an initializer list of unique values.
- @param list `std::initializer_list` of unique values.
- @param meta description of the axis.
- @param options see boost::histogram::axis::option (optional).
- @param alloc allocator instance to use.
- */
- template <class U>
- category(std::initializer_list<U> list, metadata_type meta = {},
- options_type options = {}, allocator_type alloc = {})
- : category(list.begin(), list.end(), std::move(meta), options, std::move(alloc)) {}
- // kept for backward compatibility; requires_allocator is a workaround for deduction
- // guides in gcc>=11
- template <class U, class A, class = detail::requires_allocator<A>>
- category(std::initializer_list<U> list, metadata_type meta, A alloc)
- : category(list.begin(), list.end(), std::move(meta), {}, std::move(alloc)) {}
- /// Constructor used by algorithm::reduce to shrink and rebin (not for users).
- category(const category& src, index_type begin, index_type end, unsigned merge)
- // LCOV_EXCL_START: gcc-8 is missing the delegated ctor for no reason
- : category(src.vec_.begin() + begin, src.vec_.begin() + end, src.metadata(), {},
- src.get_allocator())
- // LCOV_EXCL_STOP
- {
- if (merge > 1)
- BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for category axis"));
- }
- /// Return index for value argument.
- index_type index(const value_type& x) const noexcept {
- const auto beg = vec_.begin();
- const auto end = vec_.end();
- return static_cast<index_type>(std::distance(beg, std::find(beg, end, x)));
- }
- /// Returns index and shift (if axis has grown) for the passed argument.
- std::pair<index_type, index_type> update(const value_type& x) {
- const auto i = index(x);
- if (i < size()) return {i, 0};
- vec_.emplace_back(x);
- return {i, -1};
- }
- /// Return value for index argument.
- /// Throws `std::out_of_range` if the index is out of bounds.
- auto value(index_type idx) const
- -> std::conditional_t<std::is_scalar<value_type>::value, value_type,
- const value_type&> {
- if (idx < 0 || idx >= size())
- BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
- return vec_[idx];
- }
- /// Return value for index argument; alias for value(...).
- decltype(auto) bin(index_type idx) const { return value(idx); }
- /// Returns the number of bins, without over- or underflow.
- index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
- /// Returns the options.
- static constexpr unsigned options() noexcept { return options_type::value; }
- /// Whether the axis is inclusive (see axis::traits::is_inclusive).
- static constexpr bool inclusive() noexcept {
- return options() & (option::overflow | option::growth);
- }
- /// Indicate that the axis is not ordered.
- static constexpr bool ordered() noexcept { return false; }
- template <class V, class M, class O, class A>
- bool operator==(const category<V, M, O, A>& o) const noexcept {
- const auto& a = vec_;
- const auto& b = o.vec_;
- return std::equal(a.begin(), a.end(), b.begin(), b.end(), detail::relaxed_equal{}) &&
- detail::relaxed_equal{}(this->metadata(), o.metadata());
- }
- template <class V, class M, class O, class A>
- bool operator!=(const category<V, M, O, A>& o) const noexcept {
- return !operator==(o);
- }
- allocator_type get_allocator() const { return vec_.get_allocator(); }
- template <class Archive>
- void serialize(Archive& ar, unsigned /* version */) {
- ar& make_nvp("seq", vec_);
- ar& make_nvp("meta", this->metadata());
- }
- private:
- vector_type vec_;
- template <class V, class M, class O, class A>
- friend class category;
- };
- #if __cpp_deduction_guides >= 201606
- template <class T>
- category(std::initializer_list<T>)
- -> category<detail::replace_cstring<std::decay_t<T>>, null_type>;
- template <class T, class M>
- category(std::initializer_list<T>, M)
- -> category<detail::replace_cstring<std::decay_t<T>>,
- detail::replace_cstring<std::decay_t<M>>>;
- template <class T, class M, unsigned B>
- category(std::initializer_list<T>, M, const option::bitset<B>&)
- -> category<detail::replace_cstring<std::decay_t<T>>,
- detail::replace_cstring<std::decay_t<M>>, option::bitset<B>>;
- #endif
- } // namespace axis
- } // namespace histogram
- } // namespace boost
- #endif
|