Program Listing for File random_intersperse.hpp

Return to documentation for file (include/beluga/views/random_intersperse.hpp)

// Copyright 2024 Ekumen, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef BELUGA_VIEWS_RANDOM_INTERSPERSE_HPP
#define BELUGA_VIEWS_RANDOM_INTERSPERSE_HPP

#include <functional>
#include <optional>
#include <random>
#include <type_traits>

#include <range/v3/functional/bind_back.hpp>
#include <range/v3/utility/random.hpp>
#include <range/v3/view/adaptor.hpp>

namespace beluga::views {

namespace detail {


template <class Range, class Fn, class URNG = typename ranges::detail::default_random_engine>
struct random_intersperse_view
    : public ranges::view_adaptor<
          random_intersperse_view<Range, Fn, URNG>,
          Range,
          // The cardinality value is unknown at compile time.
          // If the adapted range cardinality is finite then we know the resulting view is finite.
          // But the intersperse probability could be 1.0, leading to an infinite range in practice.
          ranges::unknown> {
 public:
  random_intersperse_view() = default;


  constexpr random_intersperse_view(
      Range range,
      Fn fn,
      double probability,
      URNG& engine = ranges::detail::get_random_engine())
      : random_intersperse_view::view_adaptor{std::move(range)},
        fn_{std::move(fn)},
        distribution_{probability},
        engine_{std::addressof(engine)} {}

 private:
  // `ranges::range_access` needs access to the adaptor members.
  friend ranges::range_access;

  using result_type = ranges::common_type_t<decltype(std::declval<Fn>()()), ranges::range_value_t<Range>>;

  struct adaptor : public ranges::adaptor_base {
   public:
    adaptor() = default;

    constexpr explicit adaptor(random_intersperse_view* view) noexcept : view_(view) {}

    [[nodiscard]] constexpr auto read(ranges::iterator_t<Range> it) const { return fn_return_.value_or(*it); }

    constexpr void next(ranges::iterator_t<Range>& it) {
      fn_return_.reset();
      if (view_->should_intersperse()) {
        fn_return_ = view_->fn_();
      } else {
        ++it;
      }
    }

    void prev(ranges::iterator_t<Range>& it) = delete;
    void advance() = delete;
    void distance_to() = delete;

   private:
    random_intersperse_view* view_;
    std::optional<result_type> fn_return_;
  };

  [[nodiscard]] constexpr auto begin_adaptor() { return adaptor{this}; }

  [[nodiscard]] constexpr bool should_intersperse() { return distribution_(*engine_); }

  ranges::semiregular_box_t<Fn> fn_;
  std::bernoulli_distribution distribution_;
  URNG* engine_;
};

struct random_intersperse_fn {
  static constexpr double kDefaultProbability = 0.5;


  template <class Range, class Fn, class URNG = typename ranges::detail::default_random_engine>
  constexpr auto operator()(
      Range&& range,
      Fn fn,
      double probability = kDefaultProbability,
      URNG& engine = ranges::detail::get_random_engine()) const {
    // Support nullary function objects and distribution-like objects (that take a URNG).
    auto gen = [&fn, &engine]() {
      if constexpr (std::is_invocable_v<Fn>) {
        (void)(engine);  // Not used.
        return std::move(fn);
      } else {
        static_assert(std::is_invocable_v<Fn, decltype(engine)>);
        return [fn = std::move(fn), &engine]() { return fn(engine); };
      }
    }();

    return random_intersperse_view{ranges::views::all(std::forward<Range>(range)), std::move(gen), probability, engine};
  }

  template <class Range, class Fn, class URNG>
  constexpr auto operator()(Range&& range, Fn fn, double probability, std::reference_wrapper<URNG> engine) const {
    return (*this)(ranges::views::all(std::forward<Range>(range)), std::move(fn), probability, engine.get());
  }


  template <class Fn, class URNG = typename ranges::detail::default_random_engine>
  constexpr auto operator()(
      Fn fn,
      double probability = kDefaultProbability,
      URNG& engine = ranges::detail::get_random_engine()) const {
    return ranges::make_view_closure(
        ranges::bind_back(random_intersperse_fn{}, std::move(fn), probability, std::ref(engine)));
  }
};

}  // namespace detail

inline constexpr detail::random_intersperse_fn random_intersperse;

}  // namespace beluga::views

#endif