From 14392b9fd7b52bc8340953bb6a8e9f9b412676f8 Mon Sep 17 00:00:00 2001 From: Yuval Date: Sun, 29 Dec 2019 13:18:38 +0200 Subject: [PATCH] Generalized indices and arbitrary indexing support. (#1) * Generalized indices and arbitrary indexing support. * Moved array creation inside test set * more matrix tests. renamed (i,j)=>(x,y) * Removed OffsetArrays dependency * Comment tweak --- Project.toml | 7 +-- README.md | 35 ++++++++++--- src/CircularArrays.jl | 24 +++++++-- test/runtests.jl | 112 +++++++++++++++++++++++++++++------------- 4 files changed, 130 insertions(+), 48 deletions(-) diff --git a/Project.toml b/Project.toml index 9c2a134..a98527b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,14 @@ name = "CircularArrays" uuid = "dcaa3502-af75-11e8-34c7-6b8fb8855653" +license = "MIT" desc = "Arrays with fixed size and circular indexing." -url = "https://github.com/Vexatos/CircularArrays.jl" authors = ["Vexatos "] -license = "MIT" +url = "https://github.com/Vexatos/CircularArrays.jl" version = "0.1.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" [targets] -test = ["Test"] +test = ["Test", "OffsetArrays"] diff --git a/README.md b/README.md index 248ede3..c78653c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,10 @@ # CircularArrays.jl - Multi-dimensional arrays with fixed size and circular indexing -CircularArrays.jl is a small package adding the `CircularArray{T, N}` type which can be backed by any `AbstractArray{T, N}`. A `CircularArray` has a fixed size and features circular indexing across all dimensions: Indexing and assigning beyond its bounds is possible, as the end of the array is considered adjacent to its start; indices less than 1 are possible too. Iterators will still stop at the end of the array, and indexing using ranges is only possible with ranges within the bounds of the backing array. +CircularArrays.jl is a small package adding the `CircularArray{T, N}` type which can be backed by any `AbstractArray{T, N}`. A `CircularArray` has a fixed size and features circular indexing across all dimensions: Indexing and assigning beyond its bounds in both directions is possible, as the end of the array is considered adjacent to its start. `CircularArray`s have the same `axes` as the underlying backing array, and iterators only iterate over these indices. The `CircularVector{T}` type is added as an alias for `CircularArray{T, 1}`. -```julia -# CircularArrays use mod1 for their circular behaviour. -array[index] == array[mod1(index, size)] -``` - -The following functions are provided. +The following constructors are provided. ```julia # Initialize a CircularArray backed by any AbstractArray. @@ -21,6 +16,30 @@ CircularVector(arr::AbstractArray{T, 1}) where T CircularVector(initialValue::T, size::Int) where T ``` +### Examples + +```julia +julia> using CircularArrays +julia> a = CircularArray([1,2,3]); +julia> a[0:4] +5-element CircularArray{Int64,1}: + 3 + 1 + 2 + 3 + 1 +julia> using OffsetArrays +julia> i = OffsetArray(1:5,-2:2); +julia> a[i] +5-element CircularArray{Int64,1} with indices -2:2: + 1 + 2 + 3 + 1 + 2 +``` + + ### License -CircularArrays.jl is licensed under the [MIT license](LICENSE.md). By using or interacting with this software in any way, you agree to the license of this software. \ No newline at end of file +CircularArrays.jl is licensed under the [MIT license](LICENSE.md). By using or interacting with this software in any way, you agree to the license of this software. diff --git a/src/CircularArrays.jl b/src/CircularArrays.jl index bb09f26..1015ef9 100644 --- a/src/CircularArrays.jl +++ b/src/CircularArrays.jl @@ -26,19 +26,35 @@ Alias for [`CircularArray{T,1}`](@ref). """ const CircularVector{T} = CircularArray{T, 1} -@inline clamp_bounds(arr::CircularArray, I::Tuple{Vararg{Int}})::AbstractArray{Int, 1} = map(dim -> mod1(I[dim], size(arr.data, dim)), eachindex(I)) +# Copied from a method of Base.mod, for compatibility with Julia version < 1.3, +# where this method is not defined +_mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r) +@inline clamp_bounds(arr::CircularArray, I::Tuple{Vararg{Int}})::AbstractArray{Int, 1} = map(Base.splat(_mod), zip(I, axes(arr.data))) CircularArray(def::T, size) where T = CircularArray(fill(def, size)) -@inline Base.getindex(arr::CircularArray, i::Int) = @inbounds getindex(arr.data, mod1(i, size(arr.data, 1))) -@inline Base.setindex!(arr::CircularArray, v, i::Int) = @inbounds setindex!(arr.data, v, mod1(i, size(arr.data, 1))) +@inline Base.getindex(arr::CircularArray, i::Int) = @inbounds getindex(arr.data, mod(i, Base.axes1(arr.data))) +@inline Base.setindex!(arr::CircularArray, v, i::Int) = @inbounds setindex!(arr.data, v, mod(i, Base.axes1(arr.data))) @inline Base.getindex(arr::CircularArray, I::Vararg{Int}) = @inbounds getindex(arr.data, clamp_bounds(arr, I)...) @inline Base.setindex!(arr::CircularArray, v, I::Vararg{Int}) = @inbounds setindex!(arr.data, v, clamp_bounds(arr, I)...) @inline Base.size(arr::CircularArray) = size(arr.data) +@inline Base.axes(arr::CircularArray) = axes(arr.data) + +@inline Base.checkbounds(::CircularArray, _...) = nothing + +@inline _similar(arr::CircularArray, ::Type{T}, dims) where T = CircularArray(similar(arr.data,T,dims)) +@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{Base.DimOrInd, Vararg{Base.DimOrInd}}) where T = _similar(arr,T,dims) +# Ambiguity resolution with Base +@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{Int64,Vararg{Int64}}) where T = _similar(arr,T,dims) +# Ambiguity resolution with a type-pirating OffsetArrays method. See OffsetArrays issue #87. +# Ambiguity is triggered in the case similar(arr) where arr.data::OffsetArray. +# The OffsetAxis definition is copied from OffsetArrays. +const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, Base.IdentityUnitRange, Colon} +@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{OffsetAxis, Vararg{OffsetAxis}}) where T = _similar(arr,T,dims) CircularVector(data::AbstractArray{T, 1}) where T = CircularVector{T}(data) CircularVector(def::T, size::Int) where T = CircularVector{T}(fill(def, size)) Base.IndexStyle(::Type{<:CircularVector}) = IndexLinear() -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index b00db10..ae2fbb3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,39 +1,85 @@ using CircularArrays +using OffsetArrays using Test -v1 = CircularVector(rand(Int64, 5)) - @test IndexStyle(CircularArray) == IndexCartesian() @test IndexStyle(CircularVector) == IndexLinear() -@test size(v1, 1) == 5 -@test typeof(v1) == CircularVector{Int64} -@test isa(v1, CircularVector) -@test isa(v1, AbstractVector{Int}) -@test !isa(v1, AbstractVector{String}) -@test v1[2] == v1[2 + length(v1)] -v1[2] = 0 -v1[3] = 0 -@test v1[2] == v1[3] -@test_throws MethodError v1[2] = "Hello" - -v2 = CircularVector("abcde", 5) - -@test prod(v2) == "abcde"^5 - -@test_throws MethodError push!(v1, 15) - -b_arr = [2 4 6 8; 10 12 14 16; 18 20 22 24] -a1 = CircularArray(b_arr) -@test size(a1) == (3, 4) -@test a1[2, 3] == 14 -a1[2, 3] = 17 -@test a1[2, 3] == 17 -@test !isa(a1, CircularVector) -@test !isa(a1, AbstractVector) -@test isa(a1, AbstractArray) - -@test size(reshape(a1, (2, 2, 3))) == (2, 2, 3) - -a2 = CircularArray(4, (2, 3)) -@test isa(a2, CircularArray{Int, 2}) +@testset "vector" begin + data = rand(Int64, 5) + v1 = CircularVector(data) + + @test size(v1, 1) == 5 + @test typeof(v1) == CircularVector{Int64} + @test isa(v1, CircularVector) + @test isa(v1, AbstractVector{Int}) + @test !isa(v1, AbstractVector{String}) + @test v1[2] == v1[2 + length(v1)] + + @test v1[0] == data[end] + @test v1[-4:10] == [data; data; data] + @test v1[-3:1][-1] == data[end] + @test v1[[true,false,true,false,true]] == v1[[1,3,0]] + + v1copy = copy(v1) + v1_2 = v1[2] + v1[2] = 0 + v1[3] = 0 + @test v1[2] == v1[3] == 0 + @test v1copy[2] == v1_2 + @test v1copy[7] == v1_2 + @test_throws MethodError v1[2] = "Hello" + + v2 = CircularVector("abcde", 5) + + @test prod(v2) == "abcde"^5 + + @test_throws MethodError push!(v1, 15) +end + +@testset "matrix" begin + b_arr = [2 4 6 8; 10 12 14 16; 18 20 22 24] + a1 = CircularArray(b_arr) + @test size(a1) == (3, 4) + @test a1[2, 3] == 14 + a1[2, 3] = 17 + @test a1[2, 3] == 17 + @test a1[-1, 7] == 17 + @test a1[-1:5, 4:10][1, 4] == 17 + @test a1[:, -1:-1][2, 1] == 17 + @test !isa(a1, CircularVector) + @test !isa(a1, AbstractVector) + @test isa(a1, AbstractArray) + + @test size(reshape(a1, (2, 2, 3))) == (2, 2, 3) + + a2 = CircularArray(4, (2, 3)) + @test isa(a2, CircularArray{Int, 2}) +end + +@testset "offset indices" begin + i = OffsetArray(1:5,-3) + a = CircularArray(i) + @test axes(a) == axes(i) + @test a[1] == 4 + @test a[10] == a[-10] == a[0] == 3 + @test a[-2:7] == [1:5; 1:5] + @test a[0:9] == [3:5; 1:5; 1:2] + @test a[1:10][-10] == 3 + @test a[i] == OffsetArray([4,5,1,2,3],-3) + + circ_a = circshift(a,3) + @test axes(circ_a) == axes(a) + @test circ_a[1:5] == 1:5 + + j = OffsetArray([true,false,true],1) + @test a[j] == [5,2] + + data = reshape(1:9,3,3) + a = CircularArray(OffsetArray(data,-1,-1)) + @test collect(a) == data + @test all(a[x,y] == data[mod1(x+1,3),mod1(y+1,3)] for x=-10:10, y=-10:10) + @test a[i,1] == CircularArray(OffsetArray([5,6,4,5,6],-2:2)) + @test a[CartesianIndex.(i,i)] == CircularArray(OffsetArray([5,9,1,5,9],-2:2)) + @test a[a .> 4] == 5:9 +end