Skip to content

Commit

Permalink
add pairs, and keys/values for arrays
Browse files Browse the repository at this point in the history
This allows more uniform treatment of indexed collections.
  • Loading branch information
JeffBezanson committed Sep 4, 2017
1 parent fdef237 commit e2ea895
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 34 deletions.
20 changes: 15 additions & 5 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1739,7 +1749,7 @@ _sub2ind_vec(i) = ()
function ind2sub(inds::Union{DimsInteger{N},Indices{N}}, ind::AbstractVector{<:Integer}) where N
M = length(ind)
t = ntuple(n->similar(ind),Val(N))
for (i,idx) in enumerate(IndexLinear(), ind)
for (i,idx) in pairs(IndexLinear(), ind)
sub = ind2sub(inds, idx)
for j = 1:N
t[j][i] = sub[j]
Expand Down
19 changes: 17 additions & 2 deletions base/associative.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -94,7 +101,6 @@ julia> collect(keys(a))
```
"""
keys(a::Associative) = KeyIterator(a)
eachindex(a::Associative) = KeyIterator(a)

"""
values(a::Associative)
Expand All @@ -121,6 +127,15 @@ julia> collect(values(a))
"""
values(a::Associative) = ValueIterator(a)

"""
pairs(collection)
Return an iterator over `key => value` pairs for any
collection that maps a set of keys to a set of values.
This includes arrays, where the keys are the array indices.
"""
pairs(collection) = Generator(=>, keys(collection), values(collection))

function copy(a::Associative)
b = similar(a)
for (k,v) in a
Expand Down
5 changes: 5 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,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
Expand Down
10 changes: 10 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ export
mapreducedim,
merge!,
merge,
pairs,
#pop!,
#push!,
reduce,
Expand Down
37 changes: 19 additions & 18 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -134,11 +135,11 @@ size(v::IndexValue) = size(v.itr)
@propagate_inbounds function next(v::IndexValue, state)
indx, n = next(v.itr, state)
item = v.data[indx]
(indx, item), n
(indx => item), n
end
@inline done(v::IndexValue, state) = done(v.itr, state)

eltype(::Type{IndexValue{I,A}}) where {I,A} = Tuple{eltype(I), eltype(A)}
eltype(::Type{IndexValue{I,A}}) where {I,A} = Pair{eltype(I), eltype(A)}

iteratorsize(::Type{IndexValue{I}}) where {I} = iteratorsize(I)
iteratoreltype(::Type{IndexValue{I}}) where {I} = iteratoreltype(I)
Expand Down
1 change: 1 addition & 0 deletions base/number.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion base/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion base/strings/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion base/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/src/stdlib/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand Down
9 changes: 9 additions & 0 deletions test/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -835,3 +835,12 @@ 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))
local A
for (i, v) in pairs(A)
@test A[i] == v
end
@test collect(values(A)) == collect(A)
end
8 changes: 4 additions & 4 deletions test/arrayops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e2ea895

Please sign in to comment.