diff --git a/README.md b/README.md index d297eb9..d2dd5b3 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/src/ArrayIterationPlayground.jl b/src/ArrayIterationPlayground.jl index e1d4ab9..ff8c5e2 100644 --- a/src/ArrayIterationPlayground.jl +++ b/src/ArrayIterationPlayground.jl @@ -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 diff --git a/src/core.jl b/src/core.jl new file mode 100644 index 0000000..d6c5330 --- /dev/null +++ b/src/core.jl @@ -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} diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..79a400d --- /dev/null +++ b/src/types.jl @@ -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 diff --git a/test/array_types.jl b/test/array_types.jl new file mode 100644 index 0000000..4774263 --- /dev/null +++ b/test/array_types.jl @@ -0,0 +1,50 @@ +# A type to test unconventional indexing ranges + +module ATs # OffsetArrays + +using Base: ReshapedArray, ReshapedArrayIterator +using Base.PermutedDimsArrays: PermutedDimsArray +import ArrayIterationPlayground: inds + +immutable OA{T,N,AA<:AbstractArray} <: AbstractArray{T,N} + parent::AA + offsets::NTuple{N,Int} +end + +OA{T,N}(A::AbstractArray{T,N}, offsets::NTuple{N,Int}) = OA{T,N,typeof(A)}(A, offsets) + +Base.parent(A::OA) = A.parent +Base.size(A::OA) = size(parent(A)) +inds(A::OA, d) = (1:size(parent(A),d))+A.offsets[d] +Base.eachindex(A::OA) = CartesianRange(inds(A)) + +Base.getindex(A::OA, inds::Int...) = parent(A)[offset(A.offsets, inds)...] +Base.setindex!(A::OA, val, inds::Int...) = parent(A)[offset(A.offsets, inds)...] = val + +offset{N}(offsets::NTuple{N,Int}, inds::NTuple{N,Int}) = _offset((), offsets, inds) +_offset(out, ::Tuple{}, ::Tuple{}) = out +@inline _offset(out, offsets, inds) = _offset((out..., inds[1]-offsets[1]), Base.tail(offsets), Base.tail(inds)) + +# An iterator that deliberately makes PermutedDimsArrays more "dangerous" +# (sync to the rescue!) +immutable PDAIterator + iter::UnitRange{Int} +end +immutable PDAIndex + i::Int +end + +Base.parent(A::PermutedDimsArray) = A.parent # move to Base +Base.eachindex{T,N,AA<:Array}(A::PermutedDimsArray{T,N,AA}) = PDAIterator(eachindex(A.parent)) + +Base.start(iter::PDAIterator) = start(iter.iter) +Base.next(iter::PDAIterator, s) = ((i, s) = next(iter.iter, s); (PDAIndex(i), s)) +Base.done(iter::PDAIterator, s) = done(iter.iter, s) + +Base.getindex(A::PermutedDimsArray, i::PDAIndex) = parent(A)[i.i] +Base.setindex!(A::PermutedDimsArray, val, i::PDAIndex) = parent(A)[i.i] = val + +# Turn on eachindex for ReshapedArrays +Base.eachindex(A::ReshapedArray) = ReshapedArrayIterator(A) + +end diff --git a/test/dense.jl b/test/dense.jl index 979ea58..d0398d1 100644 --- a/test/dense.jl +++ b/test/dense.jl @@ -50,3 +50,140 @@ for j in inds(A, 2) @test v == A[k+=1] end end + +A = Int[1 3; 2 4] +B = Array{Int}(2, 2) +C = PermutedDimsArray(Array{Int}(2, 2), [2,1]) +R = reshape(sub(Array{Int}(3,2,3), 1:2, 1:1, 1:2), (2, 2)) + +function badcopy!(dest, src) + for (I, s) in zip(eachindex(dest), src) + dest[I] = s + end + dest +end + +fill!(B, -1) +@test badcopy!(B, A) == A +fill!(C, -1) +badcopy!(C, A) +@test C[2,1] != A[2,1] # oops! +@test C[2,1] == A[1,2] + +function goodcopy!(dest, src) + for (I, s) in sync(index(dest), src) + dest[I] = s + end + dest +end + +fill!(B, -1) +@test goodcopy!(B, A) == A +fill!(C, -1) +goodcopy!(C, A) +@test C[2,1] == A[2,1] +@test C[1,2] == A[1,2] +fill!(R, -1) +@test goodcopy!(R, A) == A + +D = ATs.OA(Array{Int}(2,2), (-1,2)) +@test_throws DimensionMismatch goodcopy!(D, A) +E = ATs.OA(A, (-1,2)) +goodcopy!(D, E) +@test D[0,3] == 1 +@test D[1,3] == 2 +@test D[0,4] == 3 +@test D[1,4] == 4 +D = ATs.OA(Array{Int}(2,2), (-2,2)) +@test_throws DimensionMismatch goodcopy!(D, E) +D = ATs.OA(Array{Int}(2,2), (-1,1)) +@test_throws DimensionMismatch goodcopy!(D, E) + +function goodcopy2!(dest, src) + for (Idest, Isrc) in sync(index(dest), index(src)) + dest[Idest] = src[Isrc] + end + dest +end + +fill!(B, -1) +@test goodcopy2!(B, A) == A +fill!(C, -1) +goodcopy2!(C, A) +@test C[2,1] == A[2,1] +@test C[1,2] == A[1,2] +fill!(R, -1) +@test goodcopy!(R, A) == A + +iter = sync(index(A), index(R)) +(IA, IR) = first(iter) +@test isa(IR, Base.ReshapedIndex) + +# TODO: uncomment when sync+stored is implemented +# function goodcopy3!(dest, src) +# for (I, s) in sync(index(dest), stored(src)) +# dest[I] = s +# end +# dest +# end + +# fill!(B, -1) +# @test goodcopy3!(B, A) == A +# fill!(C, -1) +# goodcopy3!(C, A) +# @test C[2,1] == A[2,1] +# @test C[1,2] == A[1,2] + +# 1-argument case +function mysum1a(A) + s = 0.0 + for (a,) in sync(A) + s += a + end + s +end + +function mysum1b(A) + s = 0.0 + for (I,) in sync(index(A)) + s += A[I] + end + s +end + +@test_approx_eq mysum1a(A) sum(A) +@test_approx_eq mysum1b(A) sum(A) + +# 3-argument form +function mysum!(dest, A, B) + for (Idest, a, b) in sync(index(dest), A, B) + dest[Idest] = a + b + end + dest +end + +C = PermutedDimsArray([10 30; 20 40], [2,1]) +D = fill(-1, (2,2)) +@test mysum!(D, A, C) == Int[11 23; 32 44] +D = fill(-1, (2,2)) +@test mysum!(D, C, A) == Int[11 23; 32 44] +Cc = goodcopy!(similar(A), C) +@test isa(Cc, Array) +D = fill(-1, (2,2)) +@test mysum!(D, Cc, A) == Int[11 23; 32 44] +goodcopy!(D, Cc) +mysum!(C, D, A) +@test C[1,2] == 23 +@test C[2,1] == 32 + +# sync with dimension iterators +fill!(B, -1) +for (I, a) in sync(index(B, :, 2), value(A, :, 1)) + B[I] = a +end +@test B == [-1 A[1,1]; -1 A[2,1]] +fill!(B, -1) +for (IB, IA) in sync(index(B, :, 1), index(A, :, 2)) + B[IB] = A[IA] +end +@test B == [A[1,2] -1; A[2,2] -1] diff --git a/test/internal.jl b/test/internal.jl new file mode 100644 index 0000000..475f512 --- /dev/null +++ b/test/internal.jl @@ -0,0 +1,13 @@ +A = rand(3,4) +AIP.check_sameinds(A, A) +@test_throws DimensionMismatch AIP.check_sameinds(A, A') +@test isa(AIP.storageorder(A), AIP.FirstToLast) +R = reshape(A, (2,3,2)) +@test isa(AIP.storageorder(R), AIP.FirstToLast) +S = sub(A, 1:3, 1:4) +R = reshape(S, (2,3,2)) +@test isa(AIP.storageorder(R), AIP.FirstToLast) +B = PermutedDimsArray(A, [2,1]) +@test isa(AIP.storageorder(B), AIP.OtherOrder{(2,1)}) +R = reshape(B, (2,2,3)) +@test isa(AIP.storageorder(R), AIP.NoOrder) diff --git a/test/runtests.jl b/test/runtests.jl index ae1b8a5..6912443 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,18 @@ using ArrayIterationPlayground using Base.Test +using Base.PermutedDimsArrays: PermutedDimsArray + +const AIP = ArrayIterationPlayground + +include("array_types.jl") # just for testing A = zeros(2,3) @test inds(A, 1) == 1:2 @test inds(A, 2) == 1:3 @test inds(A, 3) == 1:1 @test inds(A) == (1:2, 1:3) +B = ATs.OA(Array{Int}(2,2), (-1,2)) +@test inds(B) == (0:1, 3:4) io = IOBuffer() show(io, index(A)) @@ -23,5 +30,6 @@ io = IOBuffer() show(io, stored(index(A, 1:2, :))) @test takebuf_string(io) == "iteration hint over indexes of stored values of a "*summary(A)*" over the region (1:2,Colon())" +include("internal.jl") include("dense.jl") include("sparse.jl")