mirror of
https://github.com/boostorg/histogram.git
synced 2025-05-11 13:14:06 +00:00
make category growable and add missing deduction guides
This commit is contained in:
parent
4108c54869
commit
cadc27135f
@ -10,38 +10,46 @@
|
||||
#include <algorithm>
|
||||
#include <boost/container/new_allocator.hpp>
|
||||
#include <boost/container/string.hpp> // default meta data
|
||||
#include <boost/histogram/axis/base.hpp>
|
||||
#include <boost/container/vector.hpp>
|
||||
#include <boost/histogram/axis/iterator.hpp>
|
||||
#include <boost/histogram/detail/buffer.hpp>
|
||||
#include <boost/histogram/detail/compressed_pair.hpp>
|
||||
#include <boost/histogram/detail/meta.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace axis {
|
||||
|
||||
template <class, class, bool>
|
||||
class optional_category_mixin {};
|
||||
|
||||
/** Axis which maps unique values to bins (one on one).
|
||||
*
|
||||
* The axis maps a set of values to bins, following the order of
|
||||
* arguments in the constructor. There is an optional overflow bin
|
||||
* for this axis, which counts values that are not part of the set.
|
||||
* Binning is a O(n) operation for n values in the worst case and O(1) in
|
||||
* the best case. The value types must be equal-comparable.
|
||||
* Binning has a O(N) complexity, but with a very small factor. For
|
||||
* small N (the typical use case) it beats other kinds of lookup.
|
||||
* Value types must be equal-omparable.
|
||||
*/
|
||||
template <typename T, typename MetaData, option Options, typename Allocator>
|
||||
class category : public base<MetaData, Options>,
|
||||
public iterator_mixin<category<T, MetaData, Options, Allocator>> {
|
||||
template <class T, class MetaData, option Options, class Allocator>
|
||||
class category : public iterator_mixin<category<T, MetaData, Options, Allocator>>,
|
||||
public optional_category_mixin<category<T, MetaData, Options, Allocator>,
|
||||
T, test(Options, option::growth)> {
|
||||
static_assert(!test(Options, option::underflow), "category axis cannot have underflow");
|
||||
using base_type = base<MetaData, Options>;
|
||||
static_assert(!test(Options, option::circular), "category axis cannot be circular");
|
||||
using metadata_type = MetaData;
|
||||
using value_type = T;
|
||||
using allocator_type = Allocator;
|
||||
using vector_type = boost::container::vector<value_type, allocator_type>;
|
||||
|
||||
public:
|
||||
category() = default;
|
||||
|
||||
/** Construct from iterator range of unique values.
|
||||
*
|
||||
* \param begin begin of category range of unique values.
|
||||
@ -50,11 +58,10 @@ public:
|
||||
* \param options extra bin options.
|
||||
* \param allocator allocator instance to use.
|
||||
*/
|
||||
template <typename It, typename = detail::requires_iterator<It>>
|
||||
category(It begin, It end, metadata_type m = metadata_type(),
|
||||
allocator_type a = allocator_type())
|
||||
: base_type(std::distance(begin, end), std::move(m)), x_(nullptr, std::move(a)) {
|
||||
x_.first() = detail::create_buffer_from_iter(x_.second(), base_type::size(), begin);
|
||||
template <class It, class = detail::requires_iterator<It>>
|
||||
category(It begin, It end, metadata_type m = {}, allocator_type a = {})
|
||||
: vec_meta_(vector_type(begin, end, a), std::move(m)) {
|
||||
if (size() == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required"));
|
||||
}
|
||||
|
||||
/** Construct axis from iterable sequence of unique values.
|
||||
@ -64,9 +71,8 @@ public:
|
||||
* \param options extra bin options.
|
||||
* \param allocator allocator instance to use.
|
||||
*/
|
||||
template <typename C, typename = detail::requires_iterable<C>>
|
||||
category(const C& iterable, metadata_type m = metadata_type(),
|
||||
allocator_type a = allocator_type())
|
||||
template <class C, class = detail::requires_iterable<C>>
|
||||
category(const C& iterable, metadata_type m = {}, allocator_type a = {})
|
||||
: category(std::begin(iterable), std::end(iterable), std::move(m), std::move(a)) {}
|
||||
|
||||
/** Construct axis from an initializer list of unique values.
|
||||
@ -76,81 +82,80 @@ public:
|
||||
* \param options extra bin options.
|
||||
* \param allocator allocator instance to use.
|
||||
*/
|
||||
template <typename U>
|
||||
category(std::initializer_list<U> l, metadata_type m = metadata_type(),
|
||||
allocator_type a = allocator_type())
|
||||
template <class U>
|
||||
category(std::initializer_list<U> l, metadata_type m = {}, allocator_type a = {})
|
||||
: category(l.begin(), l.end(), std::move(m), std::move(a)) {}
|
||||
|
||||
category() : x_(nullptr) {}
|
||||
|
||||
category(const category& o) : base_type(o), x_(o.x_) {
|
||||
x_.first() =
|
||||
detail::create_buffer_from_iter(x_.second(), base_type::size(), o.x_.first());
|
||||
}
|
||||
|
||||
category& operator=(const category& o) {
|
||||
if (this != &o) {
|
||||
if (base_type::size() != o.size()) {
|
||||
detail::destroy_buffer(x_.second(), x_.first(), base_type::size());
|
||||
base_type::operator=(o);
|
||||
x_ = o.x_;
|
||||
x_.first() =
|
||||
detail::create_buffer_from_iter(x_.second(), base_type::size(), o.x_.first());
|
||||
} else {
|
||||
base_type::operator=(o);
|
||||
std::copy(o.x_.first(), o.x_.first() + base_type::size(), x_.first());
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
category(category&& o) : category() {
|
||||
using std::swap;
|
||||
swap(static_cast<base_type&>(*this), static_cast<base_type&>(o));
|
||||
swap(x_, o.x_);
|
||||
}
|
||||
|
||||
category& operator=(category&& o) {
|
||||
if (this != &o) {
|
||||
using std::swap;
|
||||
swap(static_cast<base_type&>(*this), static_cast<base_type&>(o));
|
||||
swap(x_, o.x_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~category() { detail::destroy_buffer(x_.second(), x_.first(), base_type::size()); }
|
||||
|
||||
/// Returns the bin index for the passed argument.
|
||||
int operator()(const value_type& x) const noexcept {
|
||||
const auto begin = x_.first();
|
||||
const auto end = begin + base_type::size();
|
||||
return std::distance(begin, std::find(begin, end, x));
|
||||
const auto beg = vec_meta_.first().begin();
|
||||
const auto end = vec_meta_.first().end();
|
||||
return std::distance(beg, std::find(beg, end, x));
|
||||
}
|
||||
|
||||
/// Returns the value for the bin index (performs a range check).
|
||||
decltype(auto) value(int idx) const {
|
||||
if (idx < 0 || idx >= base_type::size())
|
||||
if (idx < 0 || idx >= size())
|
||||
BOOST_THROW_EXCEPTION(std::out_of_range("category index out of range"));
|
||||
return x_.first()[idx];
|
||||
return vec_meta_.first()[idx];
|
||||
}
|
||||
|
||||
decltype(auto) operator[](int idx) const noexcept { return value(idx); }
|
||||
|
||||
/// Returns the number of bins, without extra bins.
|
||||
int size() const noexcept { return vec_meta_.first().size(); }
|
||||
/// Returns the options.
|
||||
static constexpr option options() noexcept { return Options; }
|
||||
/// Returns the metadata.
|
||||
metadata_type& metadata() noexcept { return vec_meta_.second(); }
|
||||
/// Returns the metadata (const version).
|
||||
const metadata_type& metadata() const noexcept { return vec_meta_.second(); }
|
||||
bool operator==(const category& o) const noexcept {
|
||||
return base_type::operator==(o) &&
|
||||
std::equal(x_.first(), x_.first() + base_type::size(), o.x_.first());
|
||||
const auto& a = vec_meta_.first();
|
||||
const auto& b = o.vec_meta_.first();
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end()) &&
|
||||
detail::relaxed_equal(metadata(), o.metadata());
|
||||
}
|
||||
|
||||
bool operator!=(const category& o) const noexcept { return !operator==(o); }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive&, unsigned);
|
||||
|
||||
private:
|
||||
using pointer = typename std::allocator_traits<allocator_type>::pointer;
|
||||
detail::compressed_pair<pointer, allocator_type> x_;
|
||||
detail::compressed_pair<vector_type, metadata_type> vec_meta_;
|
||||
template <class, class, bool>
|
||||
friend class optional_category_mixin;
|
||||
};
|
||||
|
||||
template <class Derived, class T>
|
||||
class optional_category_mixin<Derived, T, true> {
|
||||
using value_type = T;
|
||||
|
||||
public:
|
||||
auto update(const value_type& x) {
|
||||
auto& der = *static_cast<Derived*>(this);
|
||||
const auto i = der(x);
|
||||
if (i < der.size()) return std::make_pair(i, 0);
|
||||
der.vec_meta_.first().emplace_back(x);
|
||||
return std::make_pair(i, -1);
|
||||
}
|
||||
};
|
||||
|
||||
#if __cpp_deduction_guides >= 201606
|
||||
|
||||
template <class T>
|
||||
category(std::initializer_list<T>)->category<T>;
|
||||
|
||||
category(std::initializer_list<const char*>)->category<boost::container::string>;
|
||||
|
||||
template <class T>
|
||||
category(std::initializer_list<T>, const char*)->category<T>;
|
||||
|
||||
template <class T, class M>
|
||||
category(std::initializer_list<T>, const M&)->category<T, M>;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace axis
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
@ -192,12 +192,8 @@ template <class T, class M, option O, class A>
|
||||
template <class Archive>
|
||||
void category<T, M, O, A>::serialize(Archive& ar, unsigned /* version */) {
|
||||
// destroy must happen before base serialization with old size
|
||||
if (Archive::is_loading::value)
|
||||
detail::destroy_buffer(x_.second(), x_.first(), base_type::size());
|
||||
ar& static_cast<base_type&>(*this);
|
||||
if (Archive::is_loading::value)
|
||||
x_.first() = boost::histogram::detail::create_buffer(x_.second(), base_type::size());
|
||||
ar& boost::serialization::make_array(x_.first(), base_type::size());
|
||||
ar& vec_meta_.first();
|
||||
ar& vec_meta_.second();
|
||||
}
|
||||
|
||||
template <class... Ts>
|
||||
|
@ -50,6 +50,20 @@ int main() {
|
||||
BOOST_TEST_THROWS(a.value(3), std::out_of_range);
|
||||
}
|
||||
|
||||
// axis::category with growth
|
||||
{
|
||||
axis::category<int, axis::null_type, axis::option::growth> a;
|
||||
BOOST_TEST_EQ(a.size(), 0);
|
||||
BOOST_TEST_EQ(a.update(5), std::make_pair(0, -1));
|
||||
BOOST_TEST_EQ(a.size(), 1);
|
||||
BOOST_TEST_EQ(a.update(1), std::make_pair(1, -1));
|
||||
BOOST_TEST_EQ(a.size(), 2);
|
||||
BOOST_TEST_EQ(a.update(10), std::make_pair(2, -1));
|
||||
BOOST_TEST_EQ(a.size(), 3);
|
||||
BOOST_TEST_EQ(a.update(10), std::make_pair(2, 0));
|
||||
BOOST_TEST_EQ(a.size(), 3);
|
||||
}
|
||||
|
||||
// iterators
|
||||
{
|
||||
test_axis_iterator(axis::category<>({3, 1, 2}, ""), 0, 3);
|
||||
|
@ -86,14 +86,28 @@ int main() {
|
||||
}
|
||||
|
||||
{
|
||||
auto a0 = axis::regular(3, -1, 1, axis::null_type());
|
||||
auto a1 = axis::integer(0, 4, axis::null_type());
|
||||
axis::category a{1, 2};
|
||||
axis::category b{"x", "y"};
|
||||
axis::category c{{1, 2}, "foo"};
|
||||
axis::category d{{1, 2}, axis::null_type{}};
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((std::is_same<decltype(a), axis::category<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE(
|
||||
(std::is_same<decltype(b), axis::category<boost::container::string>>));
|
||||
BOOST_TEST_TRAIT_TRUE((std::is_same<decltype(c), axis::category<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE(
|
||||
(std::is_same<decltype(d), axis::category<int, axis::null_type>>));
|
||||
}
|
||||
|
||||
{
|
||||
auto a0 = axis::regular(3, -1, 1, axis::null_type{});
|
||||
auto a1 = axis::integer(0, 4, axis::null_type{});
|
||||
auto a = histogram(std::make_tuple(a0, a1));
|
||||
BOOST_TEST_EQ(a.rank(), 2);
|
||||
BOOST_TEST_EQ(a.axis(0), a0);
|
||||
BOOST_TEST_EQ(a.axis(1), a1);
|
||||
|
||||
auto a2 = axis::regular(5, 0, 5, axis::null_type());
|
||||
auto a2 = axis::regular(5, 0, 5, axis::null_type{});
|
||||
// don't use deduction guides for vector, support depends on stdc++ version
|
||||
std::vector<decltype(a0)> axes{{a0, a2}};
|
||||
auto b = histogram(axes, weight_storage());
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/histogram.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include "utility_histogram.hpp"
|
||||
#include "utility_meta.hpp"
|
||||
@ -15,25 +14,7 @@
|
||||
using namespace boost::histogram;
|
||||
|
||||
using integer = axis::integer<int, axis::null_type, axis::option::growth>;
|
||||
|
||||
struct category {
|
||||
int operator()(const std::string& x) const {
|
||||
auto it = set_.find(x);
|
||||
if (it != set_.end()) return it->second;
|
||||
return set_.size();
|
||||
}
|
||||
|
||||
auto update(const std::string& x) {
|
||||
const auto i = (*this)(x);
|
||||
if (i < size()) return std::make_pair(i, 0);
|
||||
set_.emplace(x, i);
|
||||
return std::make_pair(i, -1);
|
||||
}
|
||||
|
||||
int size() const { return static_cast<int>(set_.size()); }
|
||||
|
||||
std::unordered_map<std::string, int> set_;
|
||||
};
|
||||
using category = axis::category<std::string, axis::null_type, axis::option::growth>;
|
||||
|
||||
template <typename Tag>
|
||||
void run_tests() {
|
||||
@ -88,21 +69,8 @@ void run_tests() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
{
|
||||
category a;
|
||||
BOOST_TEST_EQ(a.size(), 0);
|
||||
BOOST_TEST_EQ(a.update("x"), std::make_pair(0, -1));
|
||||
BOOST_TEST_EQ(a.size(), 1);
|
||||
BOOST_TEST_EQ(a.update("y"), std::make_pair(1, -1));
|
||||
BOOST_TEST_EQ(a.size(), 2);
|
||||
BOOST_TEST_EQ(a.update("y"), std::make_pair(1, 0));
|
||||
BOOST_TEST_EQ(a.size(), 2);
|
||||
BOOST_TEST_EQ(a.update("z"), std::make_pair(2, -1));
|
||||
BOOST_TEST_EQ(a.size(), 3);
|
||||
}
|
||||
|
||||
run_tests<static_tag>();
|
||||
// run_tests<dynamic_tag>();
|
||||
run_tests<dynamic_tag>();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user