Range arithmetic

range supports slicing:

>>> range(2, 19, 3)[2:13:4]
range(8, 20, 12)

I propose extending range to support basic arithmetic:

range(a, b, c) + d == range(a + d, b + d, c)

range(a, b, c) - d == range(a - d, b - d, c)

range(a, b, c) * d == range(a * d, b * d, c * d)

-range(a, b, c) == range(-a, -b, -c)

I’ve used the * operation to “stretch” ranges and + to “shift” them.

1 Like

Do you have examples of real-world code that would benefit from this (and by “benefit” I mean something more significant than “save a couple of lines of code” or “look better from some individual’s perspective”)?

Have you considered other possible definitions? For example, your choice of behaviour for multiplication seems rather arbitrary. Can you present the justifications for your definitions, in contrast to other options?

Specifically, can you present an argument for this change which would justify the (not trivial) cost of implementing it, getting people to update training materials and books, transition planning for projects that might benefit from this but would still need to support older versions of Python, etc.?

4 Likes

When proposing an idea it helps to have answers for these questions:

Why is this a useful addition to python?

What existing code would benefit from this addition?

To answer the following:

Have you considered other possible definitions? For example, your choice of behaviour for multiplication seems rather arbitrary. Can you present the justifications for your definitions, in contrast to other options?

These operations satisfy the property that

r = range(a, b, c)
list(r + d) == [x + d for x in r]
list(r - d) == [x - d for x in r]
list(r * d) == [x * d for x in r]
list(-r) == [-x for x in r]

More concisely,

list(f(r)) == list(map(f, r))

where f is one of

partial(operator.add, d)
partial(operator.sub, d)
partial(operator.mul, d)
operator.neg
1 Like

For what it is worth, I think these are well defined operations. It is the same as one would do these on numpy.arange resulting array.

I am just not sure about usefulness. Personally never needed these nor have I seen cases where this would have been useful.

You might be using the wrong tool for the task, would be interesting to see your use cases.

5 Likes

I’ve used this in the context of np.arange, mostly with plotting. e.g. I might say np.arange(n) + 0.5 to generate the sequence 0.5, 1.5, 2.5, ..., which I prefer to np.arange(0.5, n+0.5, 0.5)

But that’s the only use case I can think of, and I’m pretty content with that as is. range doesn’t work with floats so this use-case wouldn’t even be possible with the new syntax.

range is a sequence. These overloaded operators may look quite ambiguous as to whether we mean arithmetic operations as proposed where * 2 means “ multiply each element by two”:
list(range(5) * 2) == [0, 2, 4, 6, 8]

Or sequence operations where * 2 means “repeat twice” as lists currently implement:
list(range(5) * 2) == list(range(5)) * 2 == [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

Range arithmetic manipulation is probably uncommon enough that it might actually be more confusing to special-case them from other built-in sequences. Perhaps a better solution is to document the “range recipes”, as OP has already nicely arranged, on how to transform the start stop step values should such arithmetic be ever required.

7 Likes

I agree with this comment. If you intend to do arithmetic with ranges, why aren’t you using numpy?

Then, you not only have those operations, but the entire suite of numpy operations, e.g., m @ arange(...), etc.

In my opinion, the standard library shouldn’t absorb the array-based logic that belongs in NumPy.

2 Likes

I think the key difference being ignored here is that numpy.arange eagerly produces an array with all values in the range.

The arithmetic operations would produce another range object, uses less memory and only keeps track of the bounds of the range.

I could see uses for this. Arithmetic operations on ranges would act as shifts and scaling of the interval. Arithmetic between range objects would operate just like interval arithmetic.

The way I’m thinking about this

range(a,b,1)

As an interval can be thought of as a linear function with a limited domain

E.g. f(x) = x with a domain of the integers in [a, b)

Without putting in much more thought, I’m not sure how well defined these things are for when the step of the range is not 1

Note, functionality such as this already exists in pandas so there is use cases for it.

https://pandas.pydata.org/docs/reference/api/pandas.Interval.html

1 Like

I agree, there might be a case for lazy evaluated number generators and arithmetic.

The issue with range is that by design it is limited to integers and homogeneous step.

Thus, if there is a need for such things, I would say that it is probably best to create a new object as opposed to adding complexity to range.

However, building a case for this is a bit harder than for other lazy generators.

Because the main advantage of lazy generation is not needing to store big objects in memory, while in this case one object is always a single number.

So in terms of speed, numpy (or any other typed array) will be faster and there is no memory issue here.

Also, just want to point out that regardless of several requests not a single use case has been posted yet.

This is completely different thing. Many libraries have intervals. E.g. sympy has one too.

+1 to this. The proposed behaviour here could easily be implemented as a standalone object in a 3rd party library. Obviously that wouldn’t be as convenient as having it available as the built in range object, but it would demonstrate the value (based on how popular the library was) and allow some iterations on the design to ensure the kinks get ironed out before “freezing” the design in the stdlib. And if the library doesn’t end up being particularly popular or useful, nothing’s lost - it’s still of value to the OP, and we’ve discovered it’s not worth adding to the stdlib before it’s too late.

4 Likes

They’re not and I noticed the incorrectness in my thinking as I was typing it, why I balked at going any further. I was trying to entertain OPs idea with something concrete. To be honest I think the leaps I was making is all the more reason for this not to be a great idea.

1 Like