/// \file // Range v3 library // // Copyright Casey Carter 2016 // // Use, modification and distribution is subject to 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) // // Project home: https://github.com/ericniebler/range-v3 // #ifndef RANGES_V3_VIEW_SAMPLE_HPP #define RANGES_V3_VIEW_SAMPLE_HPP #include <meta/meta.hpp> #include <range/v3/algorithm/shuffle.hpp> #include <range/v3/functional/bind_back.hpp> #include <range/v3/functional/invoke.hpp> #include <range/v3/iterator/concepts.hpp> #include <range/v3/iterator/default_sentinel.hpp> #include <range/v3/iterator/operations.hpp> #include <range/v3/range/concepts.hpp> #include <range/v3/utility/static_const.hpp> #include <range/v3/view/all.hpp> #include <range/v3/view/facade.hpp> #include <range/v3/view/view.hpp> namespace ranges { /// \cond namespace detail { template<typename Rng, bool = (bool)sized_sentinel_for<sentinel_t<Rng>, iterator_t<Rng>>> class size_tracker { range_difference_t<Rng> size_; public: CPP_assert(forward_range<Rng> || sized_range<Rng>); size_tracker() = default; size_tracker(Rng & rng) : size_(ranges::distance(rng)) {} void decrement() { --size_; } range_difference_t<Rng> get(Rng &, iterator_t<Rng> &) const { return size_; } }; // Impl for sized_sentinel_for (no need to store anything) template<typename Rng> class size_tracker<Rng, true> { public: size_tracker() = default; size_tracker(Rng &) {} void decrement() {} range_difference_t<Rng> get(Rng & rng, iterator_t<Rng> const & it) const { return ranges::end(rng) - it; } }; } // namespace detail /// \endcond /// \addtogroup group-views /// @{ // Take a random sampling from another view template<typename Rng, typename URNG> class sample_view : public view_facade<sample_view<Rng, URNG>, finite> { friend range_access; using D = range_difference_t<Rng>; Rng rng_; // Mutable is OK here because sample_view is an Input view. mutable range_difference_t<Rng> size_; URNG * engine_; template<bool IsConst> class cursor { friend cursor<!IsConst>; using Base = meta::const_if_c<IsConst, Rng>; meta::const_if_c<IsConst, sample_view> * parent_; iterator_t<Base> current_; RANGES_NO_UNIQUE_ADDRESS detail::size_tracker<Base> size_; D pop_size() { return size_.get(parent_->rng_, current_); } void advance() { if(parent_->size_ > 0) { using Dist = std::uniform_int_distribution<D>; Dist dist{}; URNG & engine = *parent_->engine_; for(;; ++current_, size_.decrement()) { RANGES_ASSERT(current_ != ranges::end(parent_->rng_)); auto n = pop_size(); RANGES_EXPECT(n > 0); typename Dist::param_type const interval{0, n - 1}; if(dist(engine, interval) < parent_->size_) break; } } } public: using value_type = range_value_t<Rng>; using difference_type = D; cursor() = default; explicit cursor(meta::const_if_c<IsConst, sample_view> * rng) : parent_(rng) , current_(ranges::begin(rng->rng_)) , size_{rng->rng_} { auto n = pop_size(); if(rng->size_ > n) rng->size_ = n; advance(); } CPP_template(bool Other)( // requires IsConst && (!Other)) cursor(cursor<Other> that) : parent_(that.parent_) , current_(std::move(that.current_)) , size_(that.size_) {} range_reference_t<Rng> read() const { return *current_; } bool equal(default_sentinel_t) const { RANGES_EXPECT(parent_); return parent_->size_ <= 0; } void next() { RANGES_EXPECT(parent_); RANGES_EXPECT(parent_->size_ > 0); --parent_->size_; RANGES_ASSERT(current_ != ranges::end(parent_->rng_)); ++current_; size_.decrement(); advance(); } }; cursor<false> begin_cursor() { return cursor<false>{this}; } template<bool Const = true> auto begin_cursor() const -> CPP_ret(cursor<Const>)( // requires Const && (sized_range<meta::const_if_c<Const, Rng>> || sized_sentinel_for<sentinel_t<meta::const_if_c<Const, Rng>>, iterator_t<meta::const_if_c<Const, Rng>>> || forward_range<meta::const_if_c<Const, Rng>>)) { return cursor<true>{this}; } public: sample_view() = default; explicit sample_view(Rng rng, D sample_size, URNG & generator) : rng_(std::move(rng)) , size_(sample_size) , engine_(std::addressof(generator)) { RANGES_EXPECT(sample_size >= 0); } Rng base() const { return rng_; } }; #if RANGES_CXX_DEDUCTION_GUIDES >= RANGES_CXX_DEDUCTION_GUIDES_17 template<typename Rng, typename URNG> sample_view(Rng &&, range_difference_t<Rng>, URNG &) ->sample_view<views::all_t<Rng>, URNG>; #endif namespace views { /// Returns a random sample of a range of length `size(range)`. struct sample_fn { private: friend view_access; #ifdef RANGES_WORKAROUND_MSVC_OLD_LAMBDA template<typename Size, typename URNG> struct lamduh { Size n; URNG & urng; template<typename Rng> auto operator()(Rng && rng) const -> invoke_result_t<sample_fn, Rng, range_difference_t<Rng>, URNG &> { return sample_fn{}(static_cast<Rng &&>(rng), static_cast<range_difference_t<Rng>>(n), urng); } }; template<typename Size, typename URNG = detail::default_random_engine> static auto CPP_fun(bind)(sample_fn, Size n, URNG & urng = detail::get_random_engine())( // requires integral<Size> && uniform_random_bit_generator<URNG>) { return make_pipeable(lamduh<Size, URNG>{std::move(n), urng}); } #else // ^^^ workaround / no workaround vvv template<typename Size, typename URNG = detail::default_random_engine> static auto CPP_fun(bind)(sample_fn, Size n, URNG & urng = detail::get_random_engine())( // requires integral<Size> && uniform_random_bit_generator<URNG>) { return make_pipeable( [n, &urng]( auto && rng) -> invoke_result_t<sample_fn, decltype(rng), range_difference_t<decltype(rng)>, URNG &> { return sample_fn{}( static_cast<decltype(rng)>(rng), static_cast<range_difference_t<decltype(rng)>>(n), urng); }); } #endif // RANGES_WORKAROUND_MSVC_OLD_LAMBDA public: template<typename Rng, typename URNG = detail::default_random_engine> auto operator()(Rng && rng, range_difference_t<Rng> sample_size, URNG & generator = detail::get_random_engine()) const -> CPP_ret(sample_view<all_t<Rng>, URNG>)( // requires viewable_range<Rng> && input_range<Rng> && uniform_random_bit_generator<URNG> && convertible_to< invoke_result_t<URNG &>, range_difference_t<Rng>> && (sized_range<Rng> || sized_sentinel_for<sentinel_t<Rng>, iterator_t<Rng>> || forward_range<Rng>)) { return sample_view<all_t<Rng>, URNG>{ all(static_cast<Rng &&>(rng)), sample_size, generator}; } }; /// \relates sample_fn /// \ingroup group-views RANGES_INLINE_VARIABLE(view<sample_fn>, sample) } // namespace views /// @} } // namespace ranges #include <range/v3/detail/satisfy_boost_range.hpp> RANGES_SATISFY_BOOST_RANGE(::ranges::sample_view) #endif