Skip to content

Commit

Permalink
Generalized indices and arbitrary indexing support. (#1)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yha authored and Vexatos committed Dec 29, 2019
1 parent fab055a commit 14392b9
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 48 deletions.
7 changes: 4 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
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"]
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
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.
24 changes: 20 additions & 4 deletions src/CircularArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
end
112 changes: 79 additions & 33 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 14392b9

Please sign in to comment.