From 9daf90ab49290eb91f0a00a837018809c16b8fd6 Mon Sep 17 00:00:00 2001 From: Mark Kittisopikul Date: Tue, 16 Feb 2021 13:50:36 -0500 Subject: [PATCH 1/2] range(; stop) and range(; length) --- NEWS.md | 1 + base/range.jl | 44 ++++++++++++++++++++++++++++++++++++++------ test/ranges.jl | 20 +++++++++++++++++++- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 36839b41fa989..6ce757a42d2e4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -45,6 +45,7 @@ Standard library changes * `count` and `findall` now accept an `AbstractChar` argument to search for a character in a string ([#38675]). * `range` now supports the `range(start, stop)` and `range(start, stop, length)` methods ([#39228]). * `range` now supports `start` as an optional keyword argument ([#38041]). +* `range` accepts either `stop` or `length` as a sole keyword argument ([#39241]) * `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 diff --git a/base/range.jl b/base/range.jl index 7278d8dc61e9b..d185930b43253 100644 --- a/base/range.jl +++ b/base/range.jl @@ -56,8 +56,10 @@ Construct a specialized array with evenly spaced elements and optimized storage 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 two of `start`, `stop`, `length`. In this case `step` will be assumed to be one. +* Call `range` with one of `stop` or `length`. `start` and `step` will be assumed to be one. + +See Extended Help for additional details on the returned type. # Examples ```jldoctest @@ -87,6 +89,15 @@ julia> range(stop=10, step=1, length=5) julia> range(start=1, step=1, stop=10) 1:1:10 + +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 @@ -101,8 +112,21 @@ To avoid this induced overhead, see the [`LinRange`](@ref) constructor. `stop` as a positional argument requires at least Julia 1.1. !!! compat "Julia 1.7" - The versions without keyword arguments and `start` as a keyword argument - require at least Julia 1.7. + The versions without keyword arguments, `start` as a keyword argument, + `stop` as a sole keyword argument, or length as a sole keyword argument + requires at least Julia 1.7. + +# Extended Help + +`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 @@ -115,8 +139,8 @@ range(;start=nothing, stop=nothing, length::Union{Integer, Nothing}=nothing, ste _range(start, step, stop, 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 ) = range_length(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) @@ -131,6 +155,14 @@ _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) +# Length as the only argument +range_length(len::Integer) = OneTo(len) + +# Stop as the only argument +range_stop(stop) = range_start_stop(oneunit(stop), stop) +range_stop(stop::Integer) = range_length(stop) + +# Stop and length as the only argument range_stop_length(a::Real, len::Integer) = UnitRange{typeof(a)}(oftype(a, a-len+1), a) range_stop_length(a::AbstractFloat, len::Integer) = range_step_stop_length(oftype(a, 1), a, len) range_stop_length(a, len::Integer) = range_step_stop_length(oftype(a-a, 1), a, len) diff --git a/test/ranges.jl b/test/ranges.jl index 937aabe471561..6d46cd522485e 100644 --- a/test/ranges.jl +++ b/test/ranges.jl @@ -18,6 +18,20 @@ # the next ones use ==, because it changes the eltype @test r == range(first(r), last(r), length(r) ) @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(; stop ) + @test o === range(; length) + @test r === range(; start, stop ) + @test r === range(; stop, length) + # the next three lines uses ==, because it changes the eltype + @test r == range(; start, stop, length) + @test r == range(; start, step, length) + @test r == range(; stop=Float64(stop)) + end for T = (Int8, Rational{Int16}, UInt32, Float64, Char) @test typeof(range(start=T(5), length=3)) === typeof(range(stop=T(5), length=3)) @@ -1450,8 +1464,12 @@ end @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(; step=1, length=6) + @test_throws ArgumentError range(; step=2, stop=7.5) @test_throws ArgumentError range(1.0, step=0.25, stop=2.0, length=5) + @test_throws ArgumentError range(; stop=nothing) + @test_throws ArgumentError range(; length=nothing) + @test_throws TypeError range(; length=5.5) end @testset "issue #23300#issuecomment-371575548" begin From e4cf9e3d46760526b9d6ae1d980329f1de35e1d8 Mon Sep 17 00:00:00 2001 From: Mark Kittisopikul Date: Fri, 6 Aug 2021 15:15:37 -0400 Subject: [PATCH 2/2] Keep compat "Julia 1.7" note intact --- base/range.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/range.jl b/base/range.jl index 073df21eaff73..1ab21c3d07d10 100644 --- a/base/range.jl +++ b/base/range.jl @@ -113,8 +113,8 @@ To avoid this induced overhead, see the [`LinRange`](@ref) constructor. `stop` as a positional argument requires at least Julia 1.1. !!! compat "Julia 1.7" - The versions without keyword arguments, `start` as a keyword argument, - requires at least Julia 1.7. + The versions without keyword arguments and `start` as a keyword argument + require at least Julia 1.7. !!! compat "Julia 1.8" The versions with `stop` as a sole keyword argument,