# Open-ended range?

I often need a range that doesn’t include the last point:

``````function func(x1, x2, del)
for x in x1:del:x2
# but I want the loop only for x != x2
``````

Depending on the values of `x1`, `x2`, and `del`, the loop sometimes includes `x == x2`.

Is there already a library function or a user-defined type to handle this case? Needless to say, you can always terminate the loop by

``````for x in x1:del:x2
(x == x2) && break
. . .
``````

Also, if you go fancy, you can define your own type with your own iterator to achieve this.

But, I just want to know what I’m missing, before writing inelegant code like the above example or going the fancy way of wring a custom type.

1 Like

Maybe

``````for x in x1:del:x2-del
...
``````

Why not just `(x1:del:x2)[1:end-1]`?

``````julia> x1 = 3; del = 2; x2 = 19;

julia> x1:del:x2
3:2:19

julia> (x1:del:x2)[1:end-1]
3:2:17
``````
``````for x in x1:del:x2-del
``````

In the initial post, I should have written that that’s not a solution:

``````function func(x1, del, x2)
display(collect(x1:del:x2))
display(collect(x1:del:(x2-del)))
end
x1 = 0.0
x2 = 2.5
delx = 1.0
func(x1, del, x2)
``````

In this case, `x1:del:x2` is what I want, because all the values satisfy `x < x2`.

Unless you know the values of `x1`, `x2`, and `del` beforehand, it’s impossible to determine whether `x2` or `x2 - del` is the right end of the UnitRange I want.

Why not just `(x1:del:x2)[1:end-1]` ?

Because you can’t tell whether `(x1:del:x2)[1:end]` or `(x1:del:x2)[1:end-1]` is the correct answer:

``````0.0:1.0:2.5 # -> [0.0, 1.0, 2.0] < 2.5  . . . I want this!
0.0:1.0:2.0 # -> [0.0, 1.0, 2.0]  . . . I don't want this because 2.0 == x2
``````

Only when you know the values of `x1`, `del`, and `x2` can you tell whether `x1:del:x2` is the right one or not.

I’m not making this up. My real need is to generate a range of `DataTime` that doesn’t include the end point:

``````using Dates
t1 = DateTime(1988,1,1)
t2 = DateTime(2023,12,31)
delt = Dates.Day(3)
t1:delt:t2 # Does this sequence include t2 ?
``````

You can’t answer the question unless you actually calculate the range `t2 - t1` in the units of days and divide it by 3.

It’s inelegant that you have to do such a calculation each time you handle different values that constitute the range.

1 Like

Ahh, felt like it was too simple. Maybe you can just remove a machine epsilon for the type? Feels hacky but works for your example.

``````julia> x = 0
0

julia> y1 = 2.5
2.5

julia> y2 = 2.0
2.0

julia> del = 1.0
1.0

julia> x:del:y1-eps(y1)
0.0:1.0:2.0

julia> x:del:y2-eps(y2)
0.0:1.0:1.0
``````

this package seems like the solution for the problem: GitHub - jw3126/RangeHelpers.jl: Make ranges not bugs, but it does not support Date ranges. (PR incoming)

3 Likes

in this particular case:

``````using RangeHelpers, Dates
t1 = DateTime(1988,1,1)
t2 = DateTime(2023,12,31)
delt = Dates.Day(3)
r = RangeHelpers.range(start = t1, step = delt, stop = strictbelow(t2))
``````

that gives:

``````julia> RangeHelpers.range(start = t1, stop = strictbelow(t2), step = delt)
DateTime("1988-01-01T00:00:00"):Day(3):DateTime("2023-12-29T00:00:00")

julia> RangeHelpers.range(start = 0.0, stop = strictbelow(2.0),step = 1.0)
0.0:1.0:1.0

julia> RangeHelpers.range(start = 0.0, stop = strictbelow(2.5),step = 1.0)
0.0:1.0:2.0

``````
4 Likes

It sure sounds like that there should be a package for that out there somewhere…

``````Iterators.takewhile(<(x2), x1:del:x2)
``````
3 Likes

Just for the sake of clearer communication, the above is not a `UnitRange`, but a `StepRangeLen`. In fact, even

``````julia> (1.0:3.0) isa UnitRange
false
``````

It’s better to just call it a ‘range’, maybe you should rename the thread to “Open-ended range?”

Maybe you can try something like (for float inputs only)

``````openrange(start, stop, step) = range(start, prevfloat(stop); step)
``````

(I also like the `takewhile`-solution of @HanD, though it seems to be slower, probably due to repeated comparisons).

2 Likes

Just for the sake of clearer communication, the above is not a `UnitRange`, but a `StepRangeLen`.
[ . . . ]
It’s better to just call it a ‘range’, maybe you should rename the thread to “Open-ended range?”

Thank you for your kind correction! Yes, I’ve edited my initial post. [Edit: I’ve also edited the other one which also included the wrong terminology.]

``````julia> function openrigthrange(sta, ste, sto)
len=Int((sto-sta) ÷ ste)+1
sto1= sta+(len-1)*ste
len-=(sto1 >=sto)
range(start=sta,length=len,step=ste)
end
openrigthrange (generic function with 1 method)

julia> openrigthrange(0.,1.1,3.0)
0.0:1.1:2.2

julia> openrigthrange(0.,1.1,3.3)
0.0:1.1:2.2
``````

I may have misunderstood you . . . Your example doesn’t compile. Did you mean that in the future the `RangeHelpers` package will work as you describe?

Here is the code

which doesn’t compile. See the error listing at the end of this message. The `range` function seems to be trying to compare an `Int` with a `Day`. I’m using `RangeHelpers v0.1.9`.

``````ERROR: LoadError: MethodError: no method matching isless(::Int64, ::Day)

Closest candidates are:
isless(::Union{Month, Quarter, Year}, ::Union{Day, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second, Week})
@ Dates ~/.julia/juliaup/julia-1.9.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.9/Dates/src/periods.jl:439
isless(::P, ::P) where P<:Period
@ Dates ~/.julia/juliaup/julia-1.9.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.9/Dates/src/periods.jl:71
isless(::Period, ::Period)
@ Dates ~/.julia/juliaup/julia-1.9.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.9/Dates/src/periods.jl:97
...

Stacktrace:
[1] <(x::Int64, y::Day)
@ Base ./operators.jl:343
[2] <=(x::Int64, y::Day)
@ Base ./operators.jl:392
[3] >=(x::Day, y::Int64)
@ Base ./operators.jl:416
[4] start_step_stop(start::DateTime, step::Day, stop::RangeHelpers.Approach{DateTime})
@ RangeHelpers ~/.julia/packages/RangeHelpers/crKui/src/RangeHelpers.jl:246
[5] range1(start::DateTime, step::Day, stop::RangeHelpers.Approach{DateTime}, length::Nothing)
@ RangeHelpers ~/.julia/packages/RangeHelpers/crKui/src/RangeHelpers.jl:231
[6] range(start::DateTime; stop::RangeHelpers.Approach{DateTime}, length::Nothing, step::Day)
@ RangeHelpers ~/.julia/packages/RangeHelpers/crKui/src/RangeHelpers.jl:141
[7] range0(start::DateTime, step::Day, stop::RangeHelpers.Approach{DateTime}, length::Nothing)
@ RangeHelpers ~/.julia/packages/RangeHelpers/crKui/src/RangeHelpers.jl:154
[8] range(; start::DateTime, stop::RangeHelpers.Approach{DateTime}, length::Nothing, step::Day)
@ RangeHelpers ~/.julia/packages/RangeHelpers/crKui/src/RangeHelpers.jl:137
[9] top-level scope
@ ~/Dropbox/tmp/try-openrange.jl:5
in expression starting at /Users/furue/Dropbox/tmp/try-openrange.jl:5
``````
1 Like

yes, the example does not compile, because of a bug in the library, that i’m fixing at the moment (0 -> `zero(step)` by longemen3000 · Pull Request #3 · jw3126/RangeHelpers.jl · GitHub)

1 Like