Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for an old version of Boost. Click here to view this page for the latest version.

boost/histogram/indexed.hpp

// Copyright 2015-2016 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_INDEXED_HPP
#define BOOST_HISTOGRAM_INDEXED_HPP

#include <boost/histogram/axis/traits.hpp>
#include <boost/histogram/detail/attribute.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <boost/iterator/iterator_adaptor.hpp>
#include <type_traits>
#include <utility>

namespace boost {
namespace histogram {

/**
  Coverage mode of the indexed range generator.

  Defines options for the iteration strategy.
*/
enum class coverage {
  inner, /*!< iterate over inner bins, exclude underflow and overflow */
  all,   /*!< iterate over all bins, including underflow and overflow */
};

/// Range over histogram bins with multi-dimensional index.
template <class Histogram>
class BOOST_HISTOGRAM_DETAIL_NODISCARD indexed_range {
  using max_dim = mp11::mp_size_t<
      detail::buffer_size<typename detail::remove_cvref_t<Histogram>::axes_type>::value>;
  using value_iterator = decltype(std::declval<Histogram>().begin());
  struct cache_item {
    int idx, begin, end, extent;
  };

public:
  /**
    Pointer-like class to access value and index of current cell.

    Its methods allow one to query the current indices and bins. Furthermore, it acts like
    a pointer to the cell value.
  */
  class accessor {
  public:
    /// Array-like view into the current multi-dimensional index.
    class index_view {
    public:
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
      class index_iterator
          : public boost::iterator_adaptor<index_iterator, const cache_item*> {
      public:
        index_iterator(const cache_item* i) : index_iterator::iterator_adaptor_(i) {}
        decltype(auto) operator*() const noexcept { return index_iterator::base()->idx; }
      };
#endif

      auto begin() const noexcept { return index_iterator(begin_); }
      auto end() const noexcept { return index_iterator(end_); }
      auto size() const noexcept { return static_cast<std::size_t>(end_ - begin_); }
      int operator[](unsigned d) const noexcept { return begin_[d].idx; }
      int at(unsigned d) const { return begin_[d].idx; }

    private:
      index_view(const cache_item* b, const cache_item* e) : begin_(b), end_(e) {}
      const cache_item *begin_, *end_;
      friend class accessor;
    };

    /// Returns the cell value.
    decltype(auto) operator*() const noexcept { return *iter_; }
    /// Returns the cell value.
    decltype(auto) get() const noexcept { return *iter_; }
    /// Access fields and methods of the cell object.
    decltype(auto) operator-> () const noexcept { return iter_; }

    /// Access current index.
    /// @param d axis dimension.
    int index(unsigned d = 0) const noexcept { return parent_.cache_[d].idx; }

    /// Access indices as an iterable range.
    auto indices() const noexcept {
      return index_view(parent_.cache_, parent_.cache_ + parent_.hist_.rank());
    }

    /// Access current bin.
    /// @tparam N axis dimension.
    template <unsigned N = 0>
    decltype(auto) bin(std::integral_constant<unsigned, N> = {}) const {
      return parent_.hist_.axis(std::integral_constant<unsigned, N>()).bin(index(N));
    }

    /// Access current bin.
    /// @param d axis dimension.
    decltype(auto) bin(unsigned d) const { return parent_.hist_.axis(d).bin(index(d)); }

    /**
      Computes density in current cell.

      The density is computed as the cell value divided by the product of bin widths. Axes
      without bin widths, like axis::category, are treated as having unit bin with.
    */
    double density() const {
      double x = 1;
      auto it = parent_.cache_;
      parent_.hist_.for_each_axis([&](const auto& a) {
        const auto w = axis::traits::width_as<double>(a, it++->idx);
        x *= w ? w : 1;
      });
      return *iter_ / x;
    }

  private:
    accessor(indexed_range& p, value_iterator i) : parent_(p), iter_(i) {}
    indexed_range& parent_;
    value_iterator iter_;
    friend class indexed_range;
  };

  class range_iterator
      : public boost::iterator_adaptor<range_iterator, value_iterator, accessor,
                                       std::forward_iterator_tag, accessor> {
  public:
    accessor operator*() const noexcept { return {*parent_, range_iterator::base()}; }

  private:
    range_iterator(indexed_range* p, value_iterator i) noexcept
        : range_iterator::iterator_adaptor_(i), parent_(p) {}

    void increment() noexcept {
      std::size_t stride = 1;
      auto c = parent_->cache_;
      ++c->idx;
      ++range_iterator::base_reference();
      while (c->idx == c->end && (c != (parent_->cache_ + parent_->hist_.rank() - 1))) {
        c->idx = c->begin;
        range_iterator::base_reference() -= (c->end - c->begin) * stride;
        stride *= c->extent;
        ++c;
        ++c->idx;
        range_iterator::base_reference() += stride;
      }
    }

    mutable indexed_range* parent_;

    friend class boost::iterator_core_access;
    friend class indexed_range;
  };

  indexed_range(Histogram& h, coverage c)
      : hist_(h), cover_all_(c == coverage::all), begin_(hist_.begin()), end_(begin_) {
    auto ca = cache_;
    const auto clast = ca + hist_.rank() - 1;
    std::size_t stride = 1;
    h.for_each_axis([&, this](const auto& a) {
      using opt = axis::traits::static_options<decltype(a)>;
      constexpr int under = opt::test(axis::option::underflow);
      constexpr int over = opt::test(axis::option::overflow);
      const auto size = a.size();

      ca->extent = size + under + over;
      // -1 if underflow and cover all, else 0
      ca->begin = cover_all_ ? -under : 0;
      // size + 1 if overflow and cover all, else size
      ca->end = cover_all_ ? size + over : size;
      ca->idx = ca->begin;

      begin_ += (ca->begin + under) * stride;
      if (ca < clast)
        end_ += (ca->begin + under) * stride;
      else
        end_ += (ca->end + under) * stride;

      stride *= ca->extent;
      ++ca;
    });
  }

  range_iterator begin() noexcept { return {this, begin_}; }
  range_iterator end() noexcept { return {nullptr, end_}; }

private:
  Histogram& hist_;
  const bool cover_all_;
  value_iterator begin_, end_;
  mutable cache_item cache_[max_dim::value];
};

/**
  Generates a range over the histogram entries.

  Use this in a range-based for loop:

  ```
  for (auto x : indexed(hist)) { ... }
  ```

  The iterators dereference to an indexed_range::accessor, which has methods to query the
  current indices and bins, and acts like a pointer to the cell value.

  @param hist Reference to the histogram.
  @param cov  Iterate over all or only inner bins (optional, default: inner).
 */
template <typename Histogram>
auto indexed(Histogram&& hist, coverage cov = coverage::inner) {
  return indexed_range<std::remove_reference_t<Histogram>>{std::forward<Histogram>(hist),
                                                           cov};
}

} // namespace histogram
} // namespace boost

#endif