Skip to content

Commit

Permalink
Merge pull request #4 from timholy/teh/sync
Browse files Browse the repository at this point in the history
Basic sync implementation; handle PermutedDimsArrays and OffsetArrays
  • Loading branch information
timholy committed Apr 25, 2016
2 parents 024273a + 943c4a4 commit 9b7be94
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 117 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

[![Build Status](https://travis-ci.org/timholy/ArrayIterationPlayground.jl.svg?branch=master)](https://travis-ci.org/timholy/ArrayIterationPlayground.jl)

This repository was designed as a place to deposit experiments related to generalizing Julia's array iteration API. An issue for general discussion can be found over at https://github.com/JuliaLang/julia/issues/15648. Initial commits tested what it would "feel like" to write code in various APIs. At present, is slowly beginning to accumulate working code.
This repository was designed as a place to deposit experiments related to generalizing Julia's array iteration API. An issue for general discussion can be found over at https://github.com/JuliaLang/julia/issues/15648. Initial commits tested what it would "feel like" to write code in various APIs. At present, is slowly beginning to accumulate working code. Your best option for learning the current state is to example the tests run by `test/runtests.jl` (including all the files it runs).
122 changes: 6 additions & 116 deletions src/ArrayIterationPlayground.jl
Original file line number Diff line number Diff line change
@@ -1,123 +1,13 @@
module ArrayIterationPlayground

using Base: ViewIndex
import Base: getindex, setindex!, start, next, done, length, eachindex, show
import Base: getindex, setindex!, start, next, done, length, eachindex, show, parent
using Base: ReshapedArray, linearindexing, LinearFast, LinearSlow
using Base.PermutedDimsArrays: PermutedDimsArray

export inds, index, stored, each

# General API

inds(A::AbstractArray, d) = 1:size(A, d)
inds{T,N}(A::AbstractArray{T,N}) = ntuple(d->inds(A,d), Val{N})

eachindex(x...) = each(index(x...))

# isindex == true => want the indexes (keys) of the array
# isindex == false => want the values of the array
# isstored == true => visit only stored entries
# isstored == false => visit all indexes
immutable ArrayIndexingWrapper{A, I<:Tuple{Vararg{ViewIndex}}, isindex, isstored}
data::A
indexes::I
end
show(io::IO, W::ArrayIndexingWrapper) = print(io, "iteration hint over ", hint_string(W), " of a ", summary(W.data), " over the region ", W.indexes)
hint_string{A,I}(::ArrayIndexingWrapper{A,I,false,false}) = "values"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,true,false}) = "indexes"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,false,true}) = "stored values"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,true,true}) = "indexes of stored values"

"""
`index(A)`
`index(A, indexes...)`
`index` creates an "iteration hint" that records the region of `A`
that you wish to iterate over. The iterator will return the indexes,
rather than values, of `A`. "iteration hints" are not iterables; to
create an iterator from a hint, call `each` on the resulting object.
In contrast to `eachindex` iteration over a subarray of `A`, the
indexes are for `A` itself.
See also: `stored`, `each`.
"""
index{A,I,isindex,isstored}(w::ArrayIndexingWrapper{A,I,isindex,isstored}) = ArrayIndexingWrapper{A,I,true,isstored}(w.data, w.indexes)

"""
`stored(A)`
`stored(A, indexes...)`
`stored` creates an "iteration hint" that records the region of `A`
that you wish to iterate over. The iterator will return just the
stored values of `A`. "iteration hints" are not iterables; to create
an iterator from a hint, call `each` on the resulting object.
See also: `index`, `each`.
"""
stored{A,I,isindex,isstored}(w::ArrayIndexingWrapper{A,I,isindex,isstored}) = ArrayIndexingWrapper{A,I,isindex,true}(w.data, w.indexes)

allindexes{T,N}(A::AbstractArray{T,N}) = ntuple(d->Colon(),Val{N})

index(A::AbstractArray) = index(A, allindexes(A))
index(A::AbstractArray, I::ViewIndex...) = index(A, I)
index{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,ViewIndex}) = ArrayIndexingWrapper{typeof(A),typeof(indexes),true,false}(A, indexes)

stored(A::AbstractArray) = stored(A, allindexes(A))
stored(A::AbstractArray, I::ViewIndex...) = stored(A, I)
stored{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,ViewIndex}) = ArrayIndexingWrapper{typeof(A),typeof(indexes),false,true}(A, indexes)

"""
`each(iterhint)`
`each(iterhint, indexes...)`
`each` instantiates the iterator associated with `iterhint`. In
conjunction with `index` and `stored`, you may choose to iterate over
either indexes or values, as well as choosing whether to iterate over
all elements or just the stored elements.
"""
each(A::AbstractArray) = each(A, allindexes(A))
each(A::AbstractArray, indexes::ViewIndex...) = each(A, indexes)
each{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,ViewIndex}) = each(ArrayIndexingWrapper{typeof(A),typeof(indexes),false,false}(A, indexes))

# Internal type for storing instantiated index iterators but returning
# array values
immutable ValueIterator{A<:AbstractArray,I}
data::A
iter::I
end

# Fallback definitions for each
each{A,I,isstored}(W::ArrayIndexingWrapper{A,I,false,isstored}) = (itr = each(index(W)); ValueIterator{A,typeof(itr)}(W.data, itr))
each{A,N,isstored}(W::ArrayIndexingWrapper{A,NTuple{N,Colon},true,isstored}) = eachindex(W.data)
each{A,I,isstored}(W::ArrayIndexingWrapper{A,I,true,isstored}) = CartesianRange(ranges(W))

start(vi::ValueIterator) = start(vi.iter)
done(vi::ValueIterator, s) = done(vi.iter, s)
next(vi::ValueIterator, s) = ((idx, s) = next(vi.iter, s); (vi.data[idx], s))

ranges(W) = ranges((), W.data, 1, W.indexes...)
ranges(out, A, d) = out
@inline ranges(out, A, d, i, I...) = ranges((out..., i), A, d+1, I...)
@inline ranges(out, A, d, i::Colon, I...) = ranges((out..., inds(A, d)), A, d+1, I...)


immutable SyncedIterator{I,F<:Tuple{Vararg{Function}}}
iter::I
itemfuns::F
end

start(iter::SyncedIterator) = start(iter.iter)
next(iter::SyncedIterator, state) = mapf(iter.itemfuns, state), next(iter.iter, state)
done(iter::SyncedIterator, state) = done(iter.iter, state)

"""
`mapf(fs, x)` is similar to `map`, except instead of mapping one
function over many objects, it maps many functions over one
object. `fs` should be a tuple-of-functions.
"""
@inline mapf(fs::Tuple, x) = _mapf((), x, fs...)
_mapf(out, x) = out
@inline _mapf(out, x, f, fs...) = _mapf((out..., f(x)), x, fs...)
export inds, index, value, stored, each, sync

include("types.jl")
include("core.jl")
include("sparse.jl")

end # module
184 changes: 184 additions & 0 deletions src/core.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# General API

inds(A::AbstractArray, d) = 1:size(A, d)
inds{T,N}(A::AbstractArray{T,N}) = ntuple(d->inds(A,d), Val{N})

eachindex(x...) = each(index(x...))

function show(io::IO, W::ArrayIndexingWrapper)
print(io, "iteration hint over ", hint_string(W), " of a ", summary(W.data), " over the region ", W.indexes)
end

parent(W::ArrayIndexingWrapper) = W.data

"""
`index(A)`
`index(A, indexes...)`
`index` creates an "iteration hint" that records the region of `A`
that you wish to iterate over. The iterator will return the indexes,
rather than values, of `A`. "iteration hints" are not iterables; to
create an iterator from a hint, call `each` on the resulting object.
In contrast to `eachindex` iteration over a subarray of `A`, the
indexes are for `A` itself.
See also: `value`, `stored`, `each`.
"""
index{A,I,isindex,isstored}(w::ArrayIndexingWrapper{A,I,isindex,isstored}) = ArrayIndexingWrapper{A,I,true,isstored}(w.data, w.indexes)

"""
`value(A)`
`value(A, indexes...)`
`value` creates an "iteration hint" that records the region of `A`
that you wish to iterate over. The iterator will return the values,
rather than the indexes, of `A`. "iteration hints" are not iterables; to
create an iterator from a hint, call `each` on the resulting object.
See also: `index`, `stored`, `each`.
"""
value{A,I,isindex,isstored}(w::ArrayIndexingWrapper{A,I,isindex,isstored}) = ArrayIndexingWrapper{A,I,true,isstored}(w.data, w.indexes)

"""
`stored(A)`
`stored(A, indexes...)`
`stored` creates an "iteration hint" that records the region of `A`
that you wish to iterate over. The iterator will return just the
stored values of `A`. "iteration hints" are not iterables; to create
an iterator from a hint, call `each` on the resulting object.
See also: `index`, `value`, `each`.
"""
stored{A,I,isindex,isstored}(w::ArrayIndexingWrapper{A,I,isindex,isstored}) = ArrayIndexingWrapper{A,I,isindex,true}(w.data, w.indexes)

allindexes{T,N}(A::AbstractArray{T,N}) = ntuple(d->Colon(),Val{N})

index(A::AbstractArray) = index(A, allindexes(A))
index(A::AbstractArray, I::IterIndex...) = index(A, I)
index{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,IterIndex}) = ArrayIndexingWrapper{typeof(A),typeof(indexes),true,false}(A, indexes)

value(A::AbstractArray) = value(A, allindexes(A))
value(A::AbstractArray, I::IterIndex...) = value(A, I)
value{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,IterIndex}) = ArrayIndexingWrapper{typeof(A),typeof(indexes),false,false}(A, indexes)

stored(A::AbstractArray) = stored(A, allindexes(A))
stored(A::AbstractArray, I::IterIndex...) = stored(A, I)
stored{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,IterIndex}) = ArrayIndexingWrapper{typeof(A),typeof(indexes),false,true}(A, indexes)

"""
`each(iterhint)`
`each(iterhint, indexes...)`
`each` instantiates the iterator associated with `iterhint`. In
conjunction with `index` and `stored`, you may choose to iterate over
either indexes or values, as well as choosing whether to iterate over
all elements or just the stored elements.
"""
each(A::AbstractArray) = each(A, allindexes(A))
each(A::AbstractArray, indexes::IterIndex...) = each(A, indexes)
each{T,N}(A::AbstractArray{T,N}, indexes::NTuple{N,IterIndex}) = each(ArrayIndexingWrapper{typeof(A),typeof(indexes),false,false}(A, indexes))

# Fallback definitions for each
each{A,I,isstored}(W::ArrayIndexingWrapper{A,I,false,isstored}) = (itr = each(index(W)); ValueIterator{A,typeof(itr)}(W.data, itr))
each{A,N,isstored}(W::ArrayIndexingWrapper{A,NTuple{N,Colon},true,isstored}) = eachindex(W.data)
each{A,I,isstored}(W::ArrayIndexingWrapper{A,I,true,isstored}) = CartesianRange(ranges(W))

start(vi::ValueIterator) = start(vi.iter)
done(vi::ValueIterator, s) = done(vi.iter, s)
next(vi::ValueIterator, s) = ((idx, s) = next(vi.iter, s); (vi.data[idx], s))

start(iter::SyncedIterator) = start(iter.iter)
next(iter::SyncedIterator, state) = mapf(iter.itemfuns, state), next(iter.iter, state)
done(iter::SyncedIterator, state) = done(iter.iter, state)

start(itr::FirstToLastIterator) = (itr.itr, start(itr.itr))
function next(itr::FirstToLastIterator, i)
idx, s = next(i[1], i[2])
itr.parent[idx], (i[1], s)
end
done(itr::FirstToLastIterator, i) = done(i[1], i[2])

function sync(A::AllElements, B::AllElements)
check_sameinds(A, B)
_sync(samestorageorder(A, B), A, B)
end

function sync(A::AllElements, B::AllElements...)
check_sameinds(A, B...)
_sync(samestorageorder(A, B...), A, B...)
end

_sync(::Type{Val{true}}, A, B) = zip(each(A), each(B))
_sync(::Type{Val{false}}, A, B) = zip(columnmajoriterator(A), columnmajoriterator(B))
_sync(::Type{Val{true}}, As...) = zip(map(each, As)...)
_sync(::Type{Val{false}}, As...) = zip(map(columnmajoriterator, As)...)

sync(A::StoredElements, B::StoredElements) = sync_stored(A, B)
sync(A, B::StoredElements) = sync_stored(A, B)
sync(A::StoredElements, B) = sync_stored(A, B)

#function sync_stored(A, B)
# check_sameinds(A, B)
#end

### Utility methods

"""
`mapf(fs, x)` is similar to `map`, except instead of mapping one
function over many objects, it maps many functions over one
object. `fs` should be a tuple-of-functions.
"""
@inline mapf(fs::Tuple, x) = _mapf((), x, fs...)
_mapf(out, x) = out
@inline _mapf(out, x, f, fs...) = _mapf((out..., f(x)), x, fs...)

storageorder(::Array) = FirstToLast()
storageorder{T,N,AA,perm}(::PermutedDimsArray{T,N,AA,perm}) = OtherOrder{perm}()
storageorder(A::ReshapedArray) = _so(storageorder(parent(A)))
storageorder(A::AbstractArray) = storageorder(parent(A)) # parent required!

storageorder(W::ArrayIndexingWrapper) = storageorder(parent(W))

_so(o::FirstToLast) = o
_so(::Any) = NoOrder() # reshape + permutedims => undefined

hint_string{A,I}(::ArrayIndexingWrapper{A,I,false,false}) = "values"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,true,false}) = "indexes"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,false,true}) = "stored values"
hint_string{A,I}(::ArrayIndexingWrapper{A,I,true,true}) = "indexes of stored values"

ranges(W) = ranges((), W.data, 1, W.indexes...)
ranges(out, A, d) = out
@inline ranges(out, A, d, i, I...) = ranges((out..., i), A, d+1, I...)
@inline ranges(out, A, d, i::Colon, I...) = ranges((out..., inds(A, d)), A, d+1, I...)

check_sameinds(::Type{Bool}, A::ArrayOrWrapper) = true
check_sameinds(::Type{Bool}, A::ArrayOrWrapper, B::ArrayOrWrapper) = extent_inds(A) == extent_inds(B)
check_sameinds(::Type{Bool}, A, B, C...) = check_sameinds(Bool, A, B) && check_sameinds(Bool, B, C...)
check_sameinds(A) = check_sameinds(Bool, A)
check_sameinds(A, B) = check_sameinds(Bool, A, B) || throw(DimensionMismatch("extent inds $(extent_inds(A)) and $(extent_inds(B)) do not match"))
check_sameinds(A, B, C...) = check_sameinds(A, B) && check_sameinds(B, C...)

# extent_inds drops sliced dimensions
extent_inds(A::AbstractArray) = inds(A)
extent_inds(W::ArrayIndexingWrapper) = _extent_inds((), W.data, 1, W.indexes...)
_extent_inds(out, A, d) = out
@inline _extent_inds(out, A, d, ::Int, indexes...) = _extent_inds(out, A, d+1, indexes...)
@inline _extent_inds(out, A, d, i, indexes...) = _extent_inds((out..., inds(A, d)), A, d+1, indexes...)

columnmajoriterator(A::AbstractArray) = columnmajoriterator(linearindexing(A), A)
columnmajoriterator(::LinearFast, A) = A
columnmajoriterator(::LinearSlow, A) = FirstToLastIterator(A, CartesianRange(size(A)))

columnmajoriterator(W::ArrayIndexingWrapper) = CartesianRange(ranges(W))

samestorageorder(A) = Val{true}
samestorageorder(A, B) = _sso(storageorder(A), storageorder(B))
samestorageorder(A, B, C...) = samestorageorder(_sso(storageorder(A), storageorder(B)), B, C...)
samestorageorder(::Type{Val{true}}, A, B...) = samestorageorder(A, B...)
samestorageorder(::Type{Val{false}}, A, B...) = Val{false}
_sso(::FirstToLast, ::FirstToLast) = Val{true}
_sso{p}(::OtherOrder{p}, ::OtherOrder{p}) = Val{true}
_sso(::StorageOrder, ::StorageOrder) = Val{false}
39 changes: 39 additions & 0 deletions src/types.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
typealias IterIndex Union{Int,UnitRange{Int},Colon}

# isindex == true => want the indexes (keys) of the array
# isindex == false => want the values of the array
# isstored == true => visit only stored entries
# isstored == false => visit all indexes
immutable ArrayIndexingWrapper{A, I<:Tuple{Vararg{IterIndex}}, isindex, isstored}
data::A
indexes::I
end

# Internal type for storing instantiated index iterators but returning
# array values
immutable ValueIterator{A<:AbstractArray,I}
data::A
iter::I
end

immutable SyncedIterator{I,F<:Tuple{Vararg{Function}}}
iter::I
itemfuns::F
end

typealias ArrayOrWrapper Union{AbstractArray,ArrayIndexingWrapper}
typealias AllElements{A,I,isindex} Union{AbstractArray,ArrayIndexingWrapper{A,I,isindex,false}}
typealias StoredElements{A,I,isindex} ArrayIndexingWrapper{A,I,isindex,true}

# storageorder has to be type-stable because it controls the output of
# sync, which is used in iteration
abstract StorageOrder
immutable FirstToLast <: StorageOrder end
immutable OtherOrder{p} <: StorageOrder end
immutable NoOrder <: StorageOrder end # combination of reshape+permutedims=>undefined

# For iterating over the *values* of an array in column-major order
immutable FirstToLastIterator{N,AA}
parent::AA
itr::CartesianRange{N}
end
Loading

0 comments on commit 9b7be94

Please sign in to comment.