Skip to content

range: Assume start=1 when not given. Use OneTo #39223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Standard library changes

* `count` and `findall` now accept an `AbstractChar` argument to search for a character in a string ([#38675]).
* `range` now supports `start` as an optional keyword argument ([#38041]).
* `range` now accepts a single positional argument as `stop` and may assume `start = 1` when `start` is not provided ([#39223])
* `islowercase` and `isuppercase` are now compliant with the Unicode lower/uppercase categories ([#38574]).
* `iseven` and `isodd` functions now support non-`Integer` numeric types ([#38976]).
* `escape_string` can now receive a collection of characters in the keyword
Expand Down
53 changes: 46 additions & 7 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,16 @@ end
range(start, stop; length, step)
range(start; length, stop, step)
range(;start, length, stop, step)
range(stop)

Construct a specialized array with evenly spaced elements and optimized storage (an [`AbstractRange`](@ref)) from the arguments.
Mathematically a range is uniquely determined by any three of `start`, `step`, `stop` and `length`.
Valid invocations of range are:
* Call `range` with any three of `start`, `step`, `stop`, `length`.
* Call `range` with two of `start`, `stop`, `length`. In this case `step` will be assumed
to be one. If both arguments are Integers, a [`UnitRange`](@ref) will be returned.
* Call `range` with fewer than three parameters. Either `stop` or `length` must be specified.
If not provided, `step` and/or `start` are assumed to be one in that order of precedence.

See Extended Help for additional details on the returned type.

# Examples
```jldoctest
Expand Down Expand Up @@ -86,6 +89,18 @@ julia> range(stop=10, step=1, length=5)

julia> range(start=1, step=1, stop=10)
1:1:10

julia> range(5)
Base.OneTo(5)

julia> range(; length = 10)
Base.OneTo(10)

julia> range(; stop = 6)
Base.OneTo(6)

julia> range(; stop = 6.5)
1.0:1.0:6.0
```
If `length` is not specified and `stop - start` is not an integer multiple of `step`, a range that ends before `stop` will be produced.
```jldoctest
Expand All @@ -103,11 +118,30 @@ If both are specified as positional arguments, one of `step` or `length` must al
`stop` as a positional argument requires at least Julia 1.1.

!!! compat "Julia 1.7"
`start` as a keyword argument requires at least Julia 1.7.
Assuming `start` is one when not provided,
`start` as a keyword argument, or
`stop` as a sole positional argument requires at least Julia 1.7.

# Extended Help

If only `length` and `stop` are provided as arguments, `step` rather than `start` is assumed
to be one.

`range` will produce a `Base.OneTo` when the arguments are Integers and
* Only `length` is provided
* Only `stop` is provided

`range` will produce a `UnitRange` when the arguments are Integers and
* Only `start` and `stop` are provided
* Only `length` and `stop` are provided

A `UnitRange` is not produced if `step` is provided even if specified as one.
"""
function range end

range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) =
stop === length === step === nothing ?
_range(nothing, nothing, start, nothing) :
_range(start, step, stop, length)

function range(start, stop; length::Union{Integer,Nothing}=nothing, step=nothing)
Expand All @@ -128,13 +162,15 @@ end
range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, step=nothing) =
_range(start, step, stop, length)

range(length::Integer) = OneTo(length)

_range(start::Nothing, step::Nothing, stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Nothing, stop::Nothing, len::Any ) = OneTo(len)
_range(start::Nothing, step::Nothing, stop::Any , len::Nothing) = range_stop(stop)
_range(start::Nothing, step::Nothing, stop::Any , len::Any ) = range_stop_length(stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Any ) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Any , len::Nothing) = range_error(start, step, stop, len)
_range(start::Nothing, step::Any , stop::Nothing, len::Any ) = range_start_step_length(oneunit(step), step, len)
_range(start::Nothing, step::Any , stop::Any , len::Nothing) = range_start_step_stop(oneunit(stop), step, stop)
_range(start::Nothing, step::Any , stop::Any , len::Any ) = range_step_stop_length(step, stop, len)
_range(start::Any , step::Nothing, stop::Nothing, len::Nothing) = range_error(start, step, stop, len)
_range(start::Any , step::Nothing, stop::Nothing, len::Any ) = range_start_length(start, len)
Expand All @@ -145,6 +181,9 @@ _range(start::Any , step::Any , stop::Nothing, len::Any ) = range_start
_range(start::Any , step::Any , stop::Any , len::Nothing) = range_start_step_stop(start, step, stop)
_range(start::Any , step::Any , stop::Any , len::Any ) = range_error(start, step, stop, len)

range_stop(stop) = oneunit(stop):stop
range_stop(stop::Integer) = Base.OneTo(stop)

range_stop_length(stop, length) = (stop-length+1):stop

range_step_stop_length(step, stop, length) = reverse(range_start_step_length(stop, -step, length))
Expand Down
18 changes: 16 additions & 2 deletions test/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@
# the next one uses ==, because it changes the eltype
@test r == range(start=first(r), stop=last(r), length=length(r))
@test r === range( stop=last(r), length=length(r))

r = 1:5
o = Base.OneTo(5)
let start=first(r), step=step(r), stop=last(r), length=length(r)
@test o === range( length)
@test o === range(; stop )
@test o === range(; length)
@test r === range(; start, stop )
@test r === range(; stop, length)
# the next four uses ==, because it changes the eltype
@test r == range(; step, stop )
@test r == range(; step, length)
@test r == range(; start, stop, length)
@test r == range(; start, step, length)
@test r == range(Float64(stop))
end
end
end

Expand Down Expand Up @@ -1443,10 +1459,8 @@ end
end

@testset "Bad range calls" begin
@test_throws ArgumentError range(1)
@test_throws ArgumentError range(nothing)
@test_throws ArgumentError range(1, step=4)
@test_throws ArgumentError range(nothing, length=2)
@test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=5)
end

Expand Down