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: arch...@mail-archive.com