diff --git a/NEWS.md b/NEWS.md index 7fed73e627ab8..5cb3574ec458d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -436,6 +436,9 @@ Library improvements defined, linear-algebra function `transpose`. Similarly, `permutedims(v::AbstractVector)` will create a row matrix ([#24839]). + * New function `only(x)` returns the one-and-only element of a collection `x`, and throws + an error if `x` contains zero or multiple elements. + Compiler/Runtime improvements ----------------------------- diff --git a/base/exports.jl b/base/exports.jl index 3a374bf678963..27370d2b5d263 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -813,6 +813,7 @@ export enumerate, # re-exported from Iterators zip, + only, # object identity and equality copy, diff --git a/base/iterators.jl b/base/iterators.jl index 5c99c4876eecf..896e8ceaca55e 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -10,7 +10,8 @@ import Base: start, done, next, isempty, length, size, eltype, iteratorsize, ite using Base: tail, tuple_type_head, tuple_type_tail, tuple_type_cons, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo, @propagate_inbounds, Generator, AbstractRange -export enumerate, zip, rest, countfrom, take, drop, cycle, repeated, product, flatten, partition +export enumerate, zip, rest, countfrom, take, drop, cycle, repeated, product, flatten, + partition, only _min_length(a, b, ::IsInfinite, ::IsInfinite) = min(length(a),length(b)) # inherit behaviour, error _min_length(a, b, A, ::IsInfinite) = length(a) @@ -914,4 +915,32 @@ function next(itr::PartitionIterator, state) return resize!(v, i), state end +""" + only(x) + +Returns the one and only element of collection `x`, and throws an error if the collection +has zero or multiple elements. +""" +Base.@propagate_inbounds function only(x) + i = start(x) + @boundscheck if done(x, i) + error("Collection is empty, must contain exactly 1 element") + end + (ret, i) = next(x, i) + @boundscheck if !done(x, i) + error("Collection has multiple elements, must contain exactly 1 element") + end + return ret end + +# Collections of known size +only(x::Tuple{}) = error("Tuple is empty, must contain exactly 1 element") +only(x::Tuple{Any}) = x[1] +only(x::Tuple) = error("Tuple contains $(length(x)) elements, must contain exactly 1 element") + +only(a::AbstractArray{<:Any, 0}) = @inbounds return a[] + +only(x::NamedTuple{<:Any, <:Tuple{Any}}) = first(x) +only(x::NamedTuple) = error("NamedTuple contains $(length(x)) elements, must contain exactly 1 element") + +end \ No newline at end of file diff --git a/base/sysimg.jl b/base/sysimg.jl index de2e4f2de9342..c5adccb19cfbd 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -193,7 +193,7 @@ end include("dict.jl") include("set.jl") include("iterators.jl") -using .Iterators: zip, enumerate +using .Iterators: zip, enumerate, only using .Iterators: Flatten, product # for generators # Definition of StridedArray diff --git a/doc/src/stdlib/iterators.md b/doc/src/stdlib/iterators.md index 7fa782ef2669f..ecf89fd4505cf 100644 --- a/doc/src/stdlib/iterators.md +++ b/doc/src/stdlib/iterators.md @@ -14,4 +14,5 @@ Base.Iterators.flatten Base.Iterators.partition Base.Iterators.filter Base.Iterators.reverse +Base.Iterators.only ``` diff --git a/test/iterators.jl b/test/iterators.jl index df453cc5f0c6b..8d68a569166b7 100644 --- a/test/iterators.jl +++ b/test/iterators.jl @@ -447,3 +447,25 @@ end @test Iterators.reverse(Iterators.reverse(t)) === t end end + +@testset "only" begin + @test only([3]) === 3 + @test_throws ErrorException only([]) + @test_throws ErrorException only([3, 2]) + + @test @inferred(only((3,))) === 3 + @test_throws ErrorException only(()) + @test_throws ErrorException only((3, 2)) + + @test only(Dict(1=>3)) === (1=>3) + @test_throws ErrorException only(Dict{Int,Int}()) + @test_throws ErrorException only(Dict(1=>3, 2=>2)) + + @test only(Set([3])) === 3 + @test_throws ErrorException only(Set(Int[])) + @test_throws ErrorException only(Set([3,2])) + + @test @inferred(only((;a=1))) === 1 + @test_throws ErrorException only(NamedTuple()) + @test_throws ErrorException only((a=3, b=2.0)) +end