4#ifndef DASMIG_CORE_WEIGHTED_TABLE_HPP
5#define DASMIG_CORE_WEIGHTED_TABLE_HPP
15#include <initializer_list>
68 for (
const auto& e :
entries)
add(e.value, e.weight);
80 const double total = (_cumulative.empty() ? 0.0 : _cumulative.back()) + weight;
81 if (!std::isfinite(total))
83 throw std::invalid_argument(
"weighted_table: total weight overflows");
85 if (weight > 0.0) _last_positive = _entries.size();
86 _entries.push_back({.value = std::move(value), .weight = weight});
87 _cumulative.push_back(total);
93 [[nodiscard]] std::size_t
size() const noexcept {
return _entries.size(); }
97 [[nodiscard]]
bool empty() const noexcept {
return _entries.empty(); }
101 [[nodiscard]] std::span<const entry>
entries() const noexcept {
return _entries; }
107 return _cumulative.empty() ? 0.0 : _cumulative.back();
118 throw std::invalid_argument(
"weighted_table: no entry has a positive weight");
120 return detail::pick_cumulative(r, _cumulative, _last_positive);
137 template <std::invocable<const T&,
double> F>
142 std::array<double, inline_sums_> inline_sums;
143 std::vector<double> heap_sums;
144 std::span<double> cumulative{inline_sums.data(), std::min(_entries.size(), inline_sums_)};
145 if (_entries.size() > inline_sums_)
147 heap_sums.resize(_entries.size());
148 cumulative = heap_sums;
150 std::size_t last_positive = 0;
152 for (std::size_t i = 0; i < _entries.size(); ++i)
154 const auto w =
static_cast<double>(adjust(_entries[i].value, _entries[i].weight));
157 if (w > 0.0) last_positive = i;
158 cumulative[i] = total;
160 if (!std::isfinite(total))
162 throw std::invalid_argument(
"weighted_table: adjusted total weight overflows");
169 return detail::pick_cumulative(r, cumulative, last_positive);
179 template <std::invocable<const T&,
double> F>
183 return i ? &_entries[*i].value :
nullptr;
193 template <std::invocable<const T&,
double> F>
194 [[nodiscard]]
const T&
pick(
rng& r,
const F& adjust)
const
196 const T* chosen =
try_pick(r, adjust);
197 if (chosen ==
nullptr)
199 throw std::invalid_argument(
"weighted_table: every adjusted weight is 0");
217 template <std::invocable<const T&,
double> F>
219 const F& adjust)
const
221 std::vector<std::size_t> chosen;
222 std::vector<bool> taken(_entries.size(),
false);
223 for (std::size_t n = 0; n < k; ++n)
225 std::size_t index = 0;
227 const bool skip = taken[index++];
228 return skip ? 0.0 :
static_cast<double>(adjust(value, w));
232 chosen.push_back(*i);
247 static void check_weight(
double w)
249 if (!std::isfinite(w) || w < 0.0)
251 throw std::invalid_argument(
"weighted_table: weights must be finite and >= 0");
255 std::vector<entry> _entries;
256 std::vector<double> _cumulative;
259 static constexpr std::size_t inline_sums_ = 64;
260 std::size_t _last_positive{0};
The core random engine: xoshiro256** (Blackman and Vigna 2018).
Values with weights, picked with probability proportional to the weight.
std::vector< std::size_t > pick_distinct_indices(rng &r, std::size_t k) const
Pick up to k distinct indices by base weight.
const T * try_pick(rng &r, const F &adjust) const
Pick a value with adjusted weights, or nothing when every adjusted weight is 0. Always consumes one e...
const T & pick(rng &r, const F &adjust) const
Pick a value with adjusted weights. Consumes one engine value.
std::size_t pick_index(rng &r) const
Pick an index by base weight. Consumes one engine value.
std::span< const entry > entries() const noexcept
The entries, in order.
bool empty() const noexcept
Whether there are no entries.
std::size_t size() const noexcept
Number of entries.
weighted_table()=default
An empty table.
std::vector< std::size_t > pick_distinct_indices(rng &r, std::size_t k, const F &adjust) const
Pick up to k distinct indices, each draw by adjusted weight among the entries not yet chosen.
weighted_table & add(T value, double weight)
Append an entry.
std::optional< std::size_t > try_pick_index(rng &r, const F &adjust) const
Pick an index with adjusted weights, or nothing when every adjusted weight is 0. Always consumes one ...
weighted_table(std::initializer_list< entry > entries)
A table from entries.
double total_weight() const noexcept
Sum of the base weights.
const T & pick(rng &r) const
Pick a value by base weight. Consumes one engine value.
Random distributions with output fixed by the core, not by the standard library.
The random engine every generator library uses: xoshiro256** seeded through SplitMix64,...
A value and its base weight.
double weight
Base weight, finite and >= 0.