From baa9a700743c05487953f2a2b714913b06f3fa13 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 21 Jul 2017 16:12:35 -0400 Subject: [PATCH] add `pairs`, and `keys`/`values` for arrays This allows more uniform treatment of indexed collections. --- base/abstractarray.jl | 18 ++++++++++++++---- base/associative.jl | 18 ++++++++++++++++-- base/deprecated.jl | 5 +++++ base/essentials.jl | 10 ++++++++++ base/exports.jl | 1 + base/iterators.jl | 33 +++++++++++++++++---------------- base/number.jl | 1 + base/process.jl | 2 +- base/strings/basic.jl | 2 +- base/test.jl | 2 +- base/tuple.jl | 4 ++-- doc/src/stdlib/collections.md | 1 + test/abstractarray.jl | 8 ++++++++ test/arrayops.jl | 8 ++++---- 14 files changed, 82 insertions(+), 31 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index e939099865fae8..88dc298735d3c1 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -69,9 +69,9 @@ function indices(A) map(OneTo, size(A)) end -# Performance optimization: get rid of a branch on `d` in `indices(A, -# d)` for d=1. 1d arrays are heavily used, and the first dimension -# comes up in other applications. +# Performance optimization: get rid of a branch on `d` in `indices(A, d)` +# for d=1. 1d arrays are heavily used, and the first dimension comes up +# in other applications. indices1(A::AbstractArray{<:Any,0}) = OneTo(1) indices1(A::AbstractArray) = (@_inline_meta; indices(A)[1]) indices1(iter) = OneTo(length(iter)) @@ -103,6 +103,10 @@ julia> extrema(b) """ linearindices(A) = (@_inline_meta; OneTo(_length(A))) linearindices(A::AbstractVector) = (@_inline_meta; indices1(A)) + +keys(a::AbstractArray) = CartesianRange(indices(a)) +keys(a::AbstractVector) = linearindices(a) + eltype(::Type{<:AbstractArray{E}}) where {E} = E elsize(::AbstractArray{T}) where {T} = sizeof(T) @@ -756,8 +760,11 @@ start(A::AbstractArray) = (@_inline_meta; itr = eachindex(A); (itr, start(itr))) next(A::AbstractArray, i) = (@_propagate_inbounds_meta; (idx, s) = next(i[1], i[2]); (A[idx], (i[1], s))) done(A::AbstractArray, i) = (@_propagate_inbounds_meta; done(i[1], i[2])) +# `eachindex` is mostly an optimization of `keys` +eachindex(itrs...) = keys(itrs...) + # eachindex iterates over all indices. IndexCartesian definitions are later. -eachindex(A::Union{Number,AbstractVector}) = (@_inline_meta(); indices1(A)) +eachindex(A::AbstractVector) = (@_inline_meta(); indices1(A)) """ eachindex(A...) @@ -826,6 +833,9 @@ end isempty(a::AbstractArray) = (_length(a) == 0) +# keys with an IndexStyle +keys(s::IndexStyle, A::AbstractArray, B::AbstractArray...) = eachindex(s, A, B...) + ## Conversions ## convert(::Type{AbstractArray{T,N}}, A::AbstractArray{T,N}) where {T,N } = A diff --git a/base/associative.jl b/base/associative.jl index 44ecd6faed3d55..be1febfed26c7d 100644 --- a/base/associative.jl +++ b/base/associative.jl @@ -69,11 +69,18 @@ end in(k, v::KeyIterator) = get(v.dict, k, secret_table_token) !== secret_table_token +""" + keys(iterator) + +For an iterator or collection that has keys and values (e.g. arrays and dictionaries), +return an iterator over the keys. +""" +function keys end """ keys(a::Associative) -Return an iterator over all keys in a collection. +Return an iterator over all keys in an associative collection. `collect(keys(a))` returns an array of keys. Since the keys are stored internally in a hash table, the order in which they are returned may vary. @@ -94,7 +101,6 @@ julia> collect(keys(a)) ``` """ keys(a::Associative) = KeyIterator(a) -eachindex(a::Associative) = KeyIterator(a) """ values(a::Associative) @@ -121,6 +127,14 @@ julia> collect(values(a)) """ values(a::Associative) = ValueIterator(a) +""" + pairs(collection) + +Return an iterator over `(index, value)` pairs for any +collection that maps a set of indices to a set of values. +""" +pairs(collection) = zip(keys(collection), values(collection)) + function copy(a::Associative) b = similar(a) for (k,v) in a diff --git a/base/deprecated.jl b/base/deprecated.jl index 2dc1cec006b6f1..fc4134af1d61d9 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1736,6 +1736,11 @@ end @deprecate promote_noncircular promote false +import .Iterators.enumerate + +@deprecate enumerate(i::IndexLinear, A::AbstractArray) pairs(i, A) +@deprecate enumerate(i::IndexCartesian, A::AbstractArray) pairs(i, A) + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/essentials.jl b/base/essentials.jl index fcb94f19822f86..bef327f36afc64 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -676,3 +676,13 @@ false ``` """ isempty(itr) = done(itr, start(itr)) + +""" + values(iterator) + +For an iterator or collection that has keys and values, return an iterator +over the values. +This function simply returns its argument by default, since the elements +of a general iterator are normally considered its "values". +""" +values(itr) = itr diff --git a/base/exports.jl b/base/exports.jl index addd53d810f7f9..608c5f46d0d8fd 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -688,6 +688,7 @@ export mapreducedim, merge!, merge, + pairs, #pop!, #push!, reduce, diff --git a/base/iterators.jl b/base/iterators.jl index d3671af58bb25c..3a6681eb4451ff 100644 --- a/base/iterators.jl +++ b/base/iterators.jl @@ -2,7 +2,7 @@ module Iterators -import Base: start, done, next, isempty, length, size, eltype, iteratorsize, iteratoreltype, indices, ndims +import Base: start, done, next, isempty, length, size, eltype, iteratorsize, iteratoreltype, indices, ndims, pairs using Base: tail, tuple_type_head, tuple_type_tail, tuple_type_cons, SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo, @propagate_inbounds @@ -78,14 +78,16 @@ struct IndexValue{I,A<:AbstractArray} end """ - enumerate(IndexLinear(), A) - enumerate(IndexCartesian(), A) - enumerate(IndexStyle(A), A) + pairs(IndexLinear(), A) + pairs(IndexCartesian(), A) + pairs(IndexStyle(A), A) An iterator that accesses each element of the array `A`, returning -`(i, x)`, where `i` is the index for the element and `x = A[i]`. This -is similar to `enumerate(A)`, except `i` will always be a valid index -for `A`. +`(i, x)`, where `i` is the index for the element and `x = A[i]`. +Identical to `pairs(A)`, except that the style of index can be selected. +Also similar to `enumerate(A)`, except `i` will be a valid index +for `A`, while `enumerate` always counts from 1 regardless of the indices +of `A`. Specifying `IndexLinear()` ensures that `i` will be an integer; specifying `IndexCartesian()` ensures that `i` will be a @@ -96,7 +98,7 @@ been defined as the native indexing style for array `A`. ```jldoctest julia> A = ["a" "d"; "b" "e"; "c" "f"]; -julia> for (index, value) in enumerate(IndexStyle(A), A) +julia> for (index, value) in pairs(IndexStyle(A), A) println("\$index \$value") end 1 a @@ -108,7 +110,7 @@ julia> for (index, value) in enumerate(IndexStyle(A), A) julia> S = view(A, 1:2, :); -julia> for (index, value) in enumerate(IndexStyle(S), S) +julia> for (index, value) in pairs(IndexStyle(S), S) println("\$index \$value") end CartesianIndex{2}((1, 1)) a @@ -117,15 +119,14 @@ CartesianIndex{2}((1, 2)) d CartesianIndex{2}((2, 2)) e ``` -Note that `enumerate(A)` returns `i` as a *counter* (always starting -at 1), whereas `enumerate(IndexLinear(), A)` returns `i` as an *index* -(starting at the first linear index of `A`, which may or may not be -1). - See also: [`IndexStyle`](@ref), [`indices`](@ref). """ -enumerate(::IndexLinear, A::AbstractArray) = IndexValue(A, linearindices(A)) -enumerate(::IndexCartesian, A::AbstractArray) = IndexValue(A, CartesianRange(indices(A))) +pairs(::IndexLinear, A::AbstractArray) = IndexValue(A, linearindices(A)) +pairs(::IndexCartesian, A::AbstractArray) = IndexValue(A, CartesianRange(indices(A))) + +# faster than zip(keys(a), values(a)) for arrays +pairs(A::AbstractArray) = pairs(IndexCartesian(), A) +pairs(A::AbstractVector) = pairs(IndexLinear(), A) length(v::IndexValue) = length(v.itr) indices(v::IndexValue) = indices(v.itr) diff --git a/base/number.jl b/base/number.jl index 4fb47f8c54cf3a..c1c22a58664233 100644 --- a/base/number.jl +++ b/base/number.jl @@ -49,6 +49,7 @@ ndims(::Type{<:Number}) = 0 length(x::Number) = 1 endof(x::Number) = 1 iteratorsize(::Type{<:Number}) = HasShape() +keys(::Number) = OneTo(1) getindex(x::Number) = x function getindex(x::Number, i::Integer) diff --git a/base/process.jl b/base/process.jl index 781e1d3ea1f94b..cc188a084e6752 100644 --- a/base/process.jl +++ b/base/process.jl @@ -822,7 +822,7 @@ wait(x::ProcessChain) = for p in x.processes; wait(p); end show(io::IO, p::Process) = print(io, "Process(", p.cmd, ", ", process_status(p), ")") # allow the elements of the Cmd to be accessed as an array or iterator -for f in (:length, :endof, :start, :eachindex, :eltype, :first, :last) +for f in (:length, :endof, :start, :keys, :eltype, :first, :last) @eval $f(cmd::Cmd) = $f(cmd.exec) end for f in (:next, :done, :getindex) diff --git a/base/strings/basic.jl b/base/strings/basic.jl index e397e782c349af..408d07504dc2c2 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -316,7 +316,7 @@ unsafe_chr2ind(s::AbstractString, i::Integer) = map_chr_ind(s, i, first, last) struct EachStringIndex{T<:AbstractString} s::T end -eachindex(s::AbstractString) = EachStringIndex(s) +keys(s::AbstractString) = EachStringIndex(s) length(e::EachStringIndex) = length(e.s) start(e::EachStringIndex) = start(e.s) diff --git a/base/test.jl b/base/test.jl index 19255d6d8e11a2..f118d105cebbce 100644 --- a/base/test.jl +++ b/base/test.jl @@ -1415,7 +1415,7 @@ end GenericArray{T}(args...) where {T} = GenericArray(Array{T}(args...)) GenericArray{T,N}(args...) where {T,N} = GenericArray(Array{T,N}(args...)) -Base.eachindex(a::GenericArray) = eachindex(a.a) +Base.keys(a::GenericArray) = keys(a.a) Base.indices(a::GenericArray) = indices(a.a) Base.length(a::GenericArray) = length(a.a) Base.size(a::GenericArray) = size(a.a) diff --git a/base/tuple.jl b/base/tuple.jl index 961107884466fd..0f4ed589fe19de 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -38,9 +38,9 @@ start(t::Tuple) = 1 done(t::Tuple, i::Int) = (length(t) < i) next(t::Tuple, i::Int) = (t[i], i+1) -eachindex(t::Tuple) = 1:length(t) +keys(t::Tuple) = 1:length(t) -function eachindex(t::Tuple, t2::Tuple...) +function keys(t::Tuple, t2::Tuple...) @_inline_meta 1:_maxlength(t, t2...) end diff --git a/doc/src/stdlib/collections.md b/doc/src/stdlib/collections.md index 9b611ed598174a..2798289a94831e 100644 --- a/doc/src/stdlib/collections.md +++ b/doc/src/stdlib/collections.md @@ -196,6 +196,7 @@ Base.delete! Base.pop!(::Any, ::Any, ::Any) Base.keys Base.values +Base.pairs Base.merge Base.merge!(::Associative, ::Associative...) Base.merge!(::Function, ::Associative, ::Associative...) diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 7baa72f37f378f..9b5b3e5b4f789b 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -835,3 +835,11 @@ end @testset "checkbounds_indices method ambiguities #20989" begin @test Base.checkbounds_indices(Bool, (1:1,), ([CartesianIndex(1)],)) end + +# keys, values, pairs +for A in (rand(2), rand(2,3)) + for (i, v) in pairs(A) + @test A[i] == v + end + @test collect(values(A)) == collect(A) +end diff --git a/test/arrayops.jl b/test/arrayops.jl index 0fe17e25612139..39b914a6a0e552 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1214,10 +1214,10 @@ end @testset "eachindexvalue" begin A14 = [11 13; 12 14] R = CartesianRange(indices(A14)) - @test [a for (a,b) in enumerate(IndexLinear(), A14)] == [1,2,3,4] - @test [a for (a,b) in enumerate(IndexCartesian(), A14)] == vec(collect(R)) - @test [b for (a,b) in enumerate(IndexLinear(), A14)] == [11,12,13,14] - @test [b for (a,b) in enumerate(IndexCartesian(), A14)] == [11,12,13,14] + @test [a for (a,b) in pairs(IndexLinear(), A14)] == [1,2,3,4] + @test [a for (a,b) in pairs(IndexCartesian(), A14)] == vec(collect(R)) + @test [b for (a,b) in pairs(IndexLinear(), A14)] == [11,12,13,14] + @test [b for (a,b) in pairs(IndexCartesian(), A14)] == [11,12,13,14] end @testset "reverse" begin