ericniebler / range-v3

Range library for C++14/17/20, basis for C++20's std::ranges
Other
4.14k stars 439 forks source link

ints() double support, step/stride syntax sugar. #102

Closed gnzlbg closed 7 years ago

gnzlbg commented 9 years ago

Does ints(0.5, 5.5) return a range of doubles [0.5, 5.5) that is incremented by 1.0 ?

If so, then the name ints is a bit misleading and otherwise, why not?

Both python xrange and D's iota support a step or a stride, as well as integers and floating point, so I think that this common case deserves some syntax sugar for creating [from, to) strided ranges.

A solution to this could be to offer both, an integer_range and a numeric_range. The numeric range could have the stride bolted in (I don't think view::strided can or should be made to work with numeric strides). The numeric_range could use by default a < b && a > b as implementation for a != b but this should probably be customizable.

Then something like:

(TODO: replace range with a better name).

anders-sjogren commented 9 years ago

For the naming and calling conventions discussion, in R it's called seqand is typically called by seq(from=...,to=...,by=...) or seq(from=...,to=...,length=...).

ericniebler commented 9 years ago

Does ints(0.5, 5.5) return a range of doubles [0.5, 5.5) that is incremented by 1.0 ?

Have you tried it?

If so, then the name ints is a bit misleading and otherwise, why not?

view::ints requires Integral, so no, it does not work. view::iota has looser constraints; view::iota(x) requires only Incrementable, and view::iota(x,y) adds EqualityComparable. But comparing doubles using op== is very bad. view::iota(0.0,5.0) is wrong given the current implementation. view::iota(0.0) | view::take(5) works though. I need to think about this.

I'm ambivalent about added stride arguments to iota and ints. That's what view::stride is for.

gnzlbg commented 9 years ago

view::iota(x) requires only Incrementable, and view::iota(x,y) adds EqualityComparable.

Thinking about it, are floats/doubles EqualityComparable or just "less than comparable"?

I'm ambivalent about added stride arguments to iota and ints. That's what view::stride is for.

It is not required at all, but I think it is a nice-to-have sugar.

On Mon, Jan 26, 2015 at 6:32 AM, Eric Niebler notifications@github.com wrote:

Does ints(0.5, 5.5) return a range of doubles [0.5, 5.5) that is incremented by 1.0 ?

Have you tried it?

If so, then the name ints is a bit misleading and otherwise, why not?

view::ints requires Integral, so no, it does not work. view::iota has looser constraints; view::iota(x) requires only Incrementable, and view::iota(x,y) adds EqualityComparable. But comparing doubles using op== is very bad. view::iota(0.0,5.0) is wrong given the current implementation. view::iota(0.0) | view::take(5) works though. I need to think about this.

I'm ambivalent about added stride arguments to iota and ints. That's what view::stride is for.

— Reply to this email directly or view it on GitHub https://github.com/ericniebler/range-v3/issues/102#issuecomment-71416402 .

sean-parent commented 9 years ago

For many uses, a more natural way to specify a numeric range is to specify an integral number of steps between two values rather than the actual increment. There are many ways to deal with the error at each step, two common ways are error accumulation and stochastic rounding. A simple method of error accumulation is to subtract off each segment and re-divide the remaining part into the remaining number of segments. This works for both floating point and integral values (it is precision agnostic). You can use the same approach with a non integral number of steps - though for sequence algorithms it is an odd notion (i.e., divide this range into 3 1/2 pieces).

On Sun, Jan 25, 2015 at 9:32 PM, Eric Niebler notifications@github.com wrote:

Does ints(0.5, 5.5) return a range of doubles [0.5, 5.5) that is incremented by 1.0 ?

Have you tried it?

If so, then the name ints is a bit misleading and otherwise, why not?

view::ints requires Integral, so no, it does not work. view::iota has looser constraints; view::iota(x) requires only Incrementable, and view::iota(x,y) adds EqualityComparable. But comparing doubles using op== is very bad. view::iota(0.0,5.0) is wrong given the current implementation. view::iota(0.0) | view::take(5) works though. I need to think about this.

I'm ambivalent about added stride arguments to iota and ints. That's what view::stride is for.

— Reply to this email directly or view it on GitHub https://github.com/ericniebler/range-v3/issues/102#issuecomment-71416402 .

ericniebler commented 9 years ago

For many uses, a more natural way to specify a numeric range is to specify an integral number of steps between two values rather than the actual increment.

I'll take your word for it, but I've never wanted to specify a numeric range that way.

There are many ways to deal with the error at each step, two common ways are error accumulation and stochastic rounding. A simple method of error accumulation is to subtract off each segment and re-divide the remaining part into the remaining number of segments. This works for both floating point and integral values (it is precision agnostic).

Interesting! But integral division is horribly slow, so you wouldn't want to do it this way for ints. Also, it doesn't work for all Incrementable types; e.g., iterators.

The existing interface is convenient for many uses and is efficient, but dangerous with floating point types. Those should probably be disallowed (or a safer implementation selected), and an interface like you suggest be provided for numeric Incrementable types, floats included -- and an optimized implementation selected for for integral types and other Incrementables like iterators.

What to call this view?

gnzlbg commented 9 years ago

For many uses, a more natural way to specify a numeric range is to specify an integral number of steps between two values rather than the actual increment.

I'll take your word for it, but I've never wanted to specify a numeric range that way.

This is extremely common in Matlab, where the function is called linspace (docs here) and used all the time, however it is a bit weird.

Generate linearly spaced vector The linspace function generates linearly spaced vectors. It is similar to the colon operator ":" but gives direct control over the number of points and always includes the endpoints.

Since:

In a recent interview, Stepanov says he wanted to name iterators linear coordinates instead, my mind made a connection here, but it is not the same thing, I like the name linear tho.

I think the name linspace in matlab comes from specifying a line, with two end points, and getting a range of points in the line. In this context, [begin, end] makes sense, and if you specify N to be 1, and begin to be 0, then returning end makes sense, since your line goes through zero, and what you get is the vector from 0 to end, which is just end.

Anyhow, I would find such a view useful, and view::linear could be a nice name.

Interesting! But integral division is horribly slow, so you wouldn't want to do it this way for ints. Also, it doesn't work for all Incrementable types; e.g., iterators.

The existing interface is convenient for many uses and is efficient, but dangerous with floating point types. Those should probably be disallowed (or a safer implementation selected), and an interface like you suggest be provided for numeric Incrementable types, floats included -- and an optimized implementation selected for for integral types and other Incrementables like iterators.

Agreed, sounds like the best way to do it.

What to call this view?

I like view::linear, makes sense for iterators, ints, floats, complex numbers...

What about the semantics?

anders-sjogren commented 9 years ago

Yes, it is also widely used in R, for example when producing a set of x-coordinates to be used when plotting (as in xs=seq(x_min, x_max, length=length); ys=f(xs); plot(xs,ys)).

sean-parent commented 9 years ago

On Wed, Jan 28, 2015 at 8:51 PM, Eric Niebler notifications@github.com wrote:

For many uses, a more natural way to specify a numeric range is to specify an integral number of steps between two values rather than the actual increment.

I'll take your word for it, but I've never wanted to specify a numeric range that way.

You probably have, you just didn't have a name for it or know it.

There are many ways to deal with the error at each step, two common ways are error accumulation and stochastic rounding. A simple method of error accumulation is to subtract off each segment and re-divide the remaining part into the remaining number of segments. This works for both floating point and integral values (it is precision agnostic).

Interesting! But integral division is horribly slow, so you wouldn't want to do it this way for ints. Also, it doesn't work for all Incrementable types; e.g., iterators.

It works for any forward incrementable type. Anything that can be transformed to a counted range.

Any time you write a recursive subdivision algorithm you likely operate on halves of length n / 2, and n - n / 2. That is the equivalent (using matlab terminology) linspace(first, last, 2). If you want to divide work for m-cores you would replace 2 with m...

Of course integer division is slow - this is why you forward difference and you get Bresenham's algorithm (useful for drawing lines as well - it is the same problem).

The existing interface is convenient for many uses and is efficient, but

dangerous with floating point types. Those should probably be disallowed (or a safer implementation selected), and an interface like you suggest be provided for numeric types, floats included -- and an optimized implementation selected for for integral types and other Incrementables like iterators.

What to call this view?

Good question - linear_spaced()? distribute()? linear_distribute()? [There are other distributions possible for the points - just as a linear distribution is equivalent to plotting a line you could have other distributions that would be the equivalent of curves. Though usually if you just say you distributing a set of points the assumption is linear unless stated otherwise.]

Sean

— Reply to this email directly or view it on GitHub https://github.com/ericniebler/range-v3/issues/102#issuecomment-71968995 .

ncrookston commented 9 years ago

For what it's worth, I've been using the range library for a few months, and have occasionally needed the function described here. Python's numpy also has linspace which permits endpoint control, while xrange has simpler behavior and is like range-v3's views.

I like the idea of calling it uniform_step or linear_step. linear seems too broad, as I can imagine other interesting linear ranges which aren't uniformly spaced points.

Nate

gnzlbg commented 9 years ago

All the proposed functions are inclusive and a superset of ints(from, to), so I don't know how much sense it makes to have two different views for creating an inclusive range with a step; ints just uses a step of 1 and works only for Integrals (I think that for ints to be an inclusive range is unfortunate).

If the name linear is too general, I'd stick to something similar to Matlab's linspace, Sean's linear_spaced seems a good suggestion to me.