Reputation: 6084
Assume that I have the values double start
, double end
and a stepsize double step
.
What is the easiest way to fill std::vector<double>
with values beginning at start
and incrementing by stepsize
as long as the current value is less than end
?
I'm asking myself, if there is an stl function making this task a one-liner.
std::vector<double> fill(double start, double end, double step) {
// Code
}
main() {
auto ret=fill(0.2, 2.3, 0.2);
// ret = {0.2, 0.4, 0.6, ... , 2.2}
}
Upvotes: 5
Views: 2651
Reputation: 69882
again out of academic interest, and probably bending the intended design of std::iota
to breaking point:
std::iota(x.begin(), x.end(), double_iota(step, min));
With the following definition of double_iota:
struct double_iota
{
double_iota(double inc, double init_value = 0.0) : _value(init_value), _inc(inc) {}
operator double() const { return _value; }
double_iota& operator++() { _value += _inc; return *this; }
double _value;
double _inc;
};
Test program:
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>
#include <iterator>
struct double_iota
{
double_iota(double inc, double init_value = 0.0) : _value(init_value), _inc(inc) {}
operator double() const { return _value; }
double_iota& operator++() { _value += _inc; return *this; }
double _value;
double _inc;
};
int main()
{
double min = 1.0;
double max = 2.3;
double step = 0.2;
std::vector<double> x(std::size_t(((max + step - std::numeric_limits<double>::epsilon()) - min) / step));
std::iota(x.begin(), x.end(), double_iota(step, min));
std::copy(x.begin(), x.end(), std::ostream_iterator<double>(std::cout, ", "));
}
expected results:
1, 1.2, 1.4, 1.6, 1.8, 2, 2.2,
update:
or we can build a custom iterator which allows us to express the sequence truly in one line:
std::vector<double> x(double_inc_iterator(min, step), double_inc_iterator(max));
as follows:
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
struct double_inc_iterator : std::iterator<std::forward_iterator_tag, double>
{
double_inc_iterator(double initial, double inc = 1.0) : _value(initial), _inc(inc) {}
value_type operator*() const { return _value; }
double_inc_iterator& operator++() { _value += _inc; return *this; }
bool operator==(double_inc_iterator const& r) const { return _value >= r._value; }
bool operator!=(double_inc_iterator const& r) const { return !(*this == r); }
value_type _value;
value_type _inc;
};
int main()
{
double min = 1.0;
double max = 2.3;
double step = 0.2;
std::vector<double> x(double_inc_iterator(min, step), double_inc_iterator(max));
std::copy(x.begin(), x.end(), std::ostream_iterator<double>(std::cout, ", "));
}
Now we don't even need the intermediate vector:
std::copy(double_inc_iterator(min, step),
double_inc_iterator(max),
std::ostream_iterator<double>(std::cout, ", "));
Upvotes: 5
Reputation: 8441
This can be done with std::partial_sum
:
#include <vector>
#include <numeric>
#include <iterator>
#include <limits>
#include <cstddef>
#include <stdexcept>
#include <iostream>
namespace detail {
template <typename T, typename S>
constexpr bool is_end_reachable(const T start, const T end, const S step) noexcept
{
return (end > start && step > 0) || start == end || (end < start && step < 0);
}
template <typename T, typename S>
constexpr std::size_t num_values(const T start, const T end, const S step) noexcept
{
// Add one as start is always included. Add epsilon to avoid floating point errors.
return (((end - start) + std::numeric_limits<T>::epsilon()) / step) + 1;
}
} // namespace detail
template <typename T, typename S>
std::vector<T> range(const T start, const T end, const S step = 1)
{
if (!detail::is_end_reachable(start, end, step)) {
throw std::invalid_argument {"end not reachable from start"};
}
std::vector<T> result(detail::num_values(start, end, step), step);
result.front() = start;
std::partial_sum(std::cbegin(result), std::cend(result), std::begin(result));
return result;
}
A few extra casts are needed for full type robustness but the idea is the same.
Example:
int main()
{
const auto v = range(-0.1, -2.7, -0.3312);
std::copy(std::cbegin(v), std::cend(v), std::ostream_iterator<double> {std::cout, " "});
std::cout << std::endl;
}
Upvotes: 0
Reputation: 10316
Just for academic purposes, you could:
std::vector<double> result;
std::generate_n(std::back_inserter(result), (size_t)((end-start)/step), [&start, step](){ auto ret=start; start+=step; return ret; });
Upvotes: 2