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…

How about this?

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