I'm personally not particularly interested in this direction. Because there
are so many options, you basically have 6 separate functions that you have
to document in one docstring, and each one is a 1- or 2-liner with
`arange`. That isn't enough semantic compression to justify a new
function, for me.

It is also the case that there is still an ambiguity in what `closed` means
in the case of a larger `step`; you excluded the option that I would have
picked (`arange(start, stop+1, step)` behaves the way I would want in such
cases; constant step, all values <= stop). `arange` is a flexible
primitive, with semantics shared by the language itself. There are lots of
ways to use that primitive to get a wide variety of results, which is what
makes it a good primitive. Documenting those 1- or 2-liners in our docs
would be preferable to having a new function, IMO.

I would be mildly more open to one additional, focused function that *just*
does a closed range (with `arange(start, stop+1, step)`) but still would
prefer not to expand our API that way.

On Thu, May 4, 2023 at 10:56 AM <cameron.pinne...@gmail.com> wrote:

> Getting a closed interval is awkward. Let's say you want an array of the
> integers from a to b, inclusive at both ends. e.g. for a=10 b=15, you want:
>
> [10, 11, 12, 13, 14, 15]
>
> Neither linspace nor arange makes this easy.
>
> arange(a, b) # wrong, it only goes up to 14
> arange(a, b+1) # right, but awkward. you have to remember to add one
> linspace(a, b, b-a) # wrong, will produce fractions
> linspace(a, b, b-a, endpoint=False) # wrong, only goes up to 14
> linspace(a, b, b-a+1) # right, awkward. also it gives you floats by default
>
> Both ways have the potential for an off-by-one error. I forget the right
> way to do it and have to look it up, pretty much every time.
>
> It has been proposed before to add an step parameter to linspace or an
> endpoint parameter to arange: https://github.com/numpy/numpy/issues/630
> but it was rejected on the grounds that it's too hard / impossible to make
> it work properly with floats.
>
> But that's not a problem if the function is restricted to only give
> integers in the first place. There's a few ways this can be done; one is to
> make a function "crange" (closed range):
>
> crange(10, 15) # 10, 11, 12, 13, 14, 15
>
> Like arange it would take a step parameter, defaulting to 1. Unlike
> arange, the start, stop, and step parameters must all be integers.
>
> This raises the question of what to do when the step size doesn't evenly
> divide the range. What should crange(10, 15, 3) return? There's no clear
> answer. There are three constraints we might be interested in:
>
> 1: for 0 <= i < len(array)-1: array[i] + step == array[i+1]
> 2: for x in array: start <= x <= stop
> 3: array[0] == start, array[-1] == stop
>
> But for crange(10, 15, 3) we can only choose two:
>
> [10, 13, 15] # violates 1, satisfies 2 and 3
> [10, 13] # violates 2, satisfies 1 and 3
> [10, 13, 16] # violates 3, satisfies 1 and 2
>
> Depending on the situation you might plausibly want any one of these.
> Furthermore, there is the symmetric set of possibilities where we fix the
> stop bound, and step backwards from it, letting the start bound vary:
>
> [10, 12, 15] # violates 1, satisfies 2 and 3
> [9, 12, 15] # violates 2, satisfies 1 and 3
> [12, 15] # violates 3, satisfies 1 and 2
>
> There are two axes of behaviour here: which bound we should "anchor", and
> what to do with the other bound. We can "anchor" either the start or the
> stop, and with the other bound we have three choices: "open", "closed", or
> "exact". Since "crange" now seems wrongly named, call it "intrange":
>
> def intrange(start: int, stop: int, step: int = 1, bound: str = "open",
> anchor: str = "start", dtype=None): ...
>
> Would behave like:
>
> intrange(10, 15, 3)
> [10, 13] # same as arange
> intrange(10, 15, 3, bound="closed")
> [10, 13, 16]
> intrange(10, 15, 3, bound="exact")
> [10, 13, 15]
> intrange(10, 15, 3, bound="open", anchor="stop")
> [12, 15]
> intrange(10, 15, 3, bound="closed", anchor="stop")
> [9, 12, 15]
> intrange(10, 15, 3, bound="exact", anchor="stop")
> [10, 12, 15]
>
> Anyway, the two most common kinds of integer range you'd want to make are
> easy .. mostly you just want step=1, inclusive at the start, and either
> exclusive or inclusive at the end.
>
> intrange(10, 15)
> [10, 11, 12, 13, 14] # same as arange
> intrange(10, 15, bound="closed")
> [10, 11, 12, 13, 14, 15] # easy to remember, intuitive, no off-by-one-error
>
> >From there, you can make inclusive floating point ranges by scaling:
>
> intrange(10, 15, bound="closed") / 10
> [1.0, 1.1, 1.2, 1.3, 1.4, 1.5]
>
> This way, we don't have to worry about floating point imprecision screwing
> up the bounds calculation.
>
> Sketch implementation:
>
> def intrange(start, stop, step=1, bound="open", anchor="start"):
>     match (anchor, bound):
>         case ("start", "open"):
>             return np.arange(start, stop, step)
>         case ("start", "closed"):
>             return np.arange(start, stop + step, step)
>         case ("start", "exact"):
>             result = np.arange(start, stop + step, step)
>             result[-1] = stop
>             return result
>         case ("stop", "open"):
>             return np.flip(np.arange(stop, start, -step))
>         case ("stop", "closed"):
>             return np.flip(np.arange(stop, start - step, -step))
>         case ("stop", "exact"):
>             result = np.flip(np.arange(stop, start - step, -step))
>             result[0] = start
>             return result
>         case _:
>             assert False
> _______________________________________________
> NumPy-Discussion mailing list -- numpy-discussion@python.org
> To unsubscribe send an email to numpy-discussion-le...@python.org
> https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
> Member address: robert.k...@gmail.com
>


-- 
Robert Kern
_______________________________________________
NumPy-Discussion mailing list -- numpy-discussion@python.org
To unsubscribe send an email to numpy-discussion-le...@python.org
https://mail.python.org/mailman3/lists/numpy-discussion.python.org/
Member address: arch...@mail-archive.com

Reply via email to