make category growable and add missing deduction guides

This commit is contained in:
Hans Dembinski 2019-01-15 00:13:04 +01:00
parent 4108c54869
commit cadc27135f
5 changed files with 110 additions and 113 deletions

View File

@ -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

View File

@ -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>

View File

@ -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);

View File

@ -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());

View File

@ -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();
}