Skip to content

Commit 14392b9

Browse files
yhaVexatos
authored andcommitted
Generalized indices and arbitrary indexing support. (Vexatos#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
1 parent fab055a commit 14392b9

File tree

4 files changed

+130
-48
lines changed

4 files changed

+130
-48
lines changed

Project.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
name = "CircularArrays"
22
uuid = "dcaa3502-af75-11e8-34c7-6b8fb8855653"
3+
license = "MIT"
34
desc = "Arrays with fixed size and circular indexing."
4-
url = "https://github.com/Vexatos/CircularArrays.jl"
55
authors = ["Vexatos <Stuarzt@gmx.de>"]
6-
license = "MIT"
6+
url = "https://github.com/Vexatos/CircularArrays.jl"
77
version = "0.1.0"
88

99
[extras]
1010
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
11+
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
1112

1213
[targets]
13-
test = ["Test"]
14+
test = ["Test", "OffsetArrays"]

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
# CircularArrays.jl - Multi-dimensional arrays with fixed size and circular indexing
22

3-
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.
3+
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.
44

55
The `CircularVector{T}` type is added as an alias for `CircularArray{T, 1}`.
66

7-
```julia
8-
# CircularArrays use mod1 for their circular behaviour.
9-
array[index] == array[mod1(index, size)]
10-
```
11-
12-
The following functions are provided.
7+
The following constructors are provided.
138

149
```julia
1510
# Initialize a CircularArray backed by any AbstractArray.
@@ -21,6 +16,30 @@ CircularVector(arr::AbstractArray{T, 1}) where T
2116
CircularVector(initialValue::T, size::Int) where T
2217
```
2318

19+
### Examples
20+
21+
```julia
22+
julia> using CircularArrays
23+
julia> a = CircularArray([1,2,3]);
24+
julia> a[0:4]
25+
5-element CircularArray{Int64,1}:
26+
3
27+
1
28+
2
29+
3
30+
1
31+
julia> using OffsetArrays
32+
julia> i = OffsetArray(1:5,-2:2);
33+
julia> a[i]
34+
5-element CircularArray{Int64,1} with indices -2:2:
35+
1
36+
2
37+
3
38+
1
39+
2
40+
```
41+
42+
2443
### License
2544

26-
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.
45+
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.

src/CircularArrays.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,35 @@ Alias for [`CircularArray{T,1}`](@ref).
2626
"""
2727
const CircularVector{T} = CircularArray{T, 1}
2828

29-
@inline clamp_bounds(arr::CircularArray, I::Tuple{Vararg{Int}})::AbstractArray{Int, 1} = map(dim -> mod1(I[dim], size(arr.data, dim)), eachindex(I))
29+
# Copied from a method of Base.mod, for compatibility with Julia version < 1.3,
30+
# where this method is not defined
31+
_mod(i::Integer, r::AbstractUnitRange{<:Integer}) = mod(i-first(r), length(r)) + first(r)
32+
@inline clamp_bounds(arr::CircularArray, I::Tuple{Vararg{Int}})::AbstractArray{Int, 1} = map(Base.splat(_mod), zip(I, axes(arr.data)))
3033

3134
CircularArray(def::T, size) where T = CircularArray(fill(def, size))
3235

33-
@inline Base.getindex(arr::CircularArray, i::Int) = @inbounds getindex(arr.data, mod1(i, size(arr.data, 1)))
34-
@inline Base.setindex!(arr::CircularArray, v, i::Int) = @inbounds setindex!(arr.data, v, mod1(i, size(arr.data, 1)))
36+
@inline Base.getindex(arr::CircularArray, i::Int) = @inbounds getindex(arr.data, mod(i, Base.axes1(arr.data)))
37+
@inline Base.setindex!(arr::CircularArray, v, i::Int) = @inbounds setindex!(arr.data, v, mod(i, Base.axes1(arr.data)))
3538
@inline Base.getindex(arr::CircularArray, I::Vararg{Int}) = @inbounds getindex(arr.data, clamp_bounds(arr, I)...)
3639
@inline Base.setindex!(arr::CircularArray, v, I::Vararg{Int}) = @inbounds setindex!(arr.data, v, clamp_bounds(arr, I)...)
3740
@inline Base.size(arr::CircularArray) = size(arr.data)
41+
@inline Base.axes(arr::CircularArray) = axes(arr.data)
42+
43+
@inline Base.checkbounds(::CircularArray, _...) = nothing
44+
45+
@inline _similar(arr::CircularArray, ::Type{T}, dims) where T = CircularArray(similar(arr.data,T,dims))
46+
@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{Base.DimOrInd, Vararg{Base.DimOrInd}}) where T = _similar(arr,T,dims)
47+
# Ambiguity resolution with Base
48+
@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{Int64,Vararg{Int64}}) where T = _similar(arr,T,dims)
49+
# Ambiguity resolution with a type-pirating OffsetArrays method. See OffsetArrays issue #87.
50+
# Ambiguity is triggered in the case similar(arr) where arr.data::OffsetArray.
51+
# The OffsetAxis definition is copied from OffsetArrays.
52+
const OffsetAxis = Union{Integer, UnitRange, Base.OneTo, Base.IdentityUnitRange, Colon}
53+
@inline Base.similar(arr::CircularArray, ::Type{T}, dims::Tuple{OffsetAxis, Vararg{OffsetAxis}}) where T = _similar(arr,T,dims)
3854

3955
CircularVector(data::AbstractArray{T, 1}) where T = CircularVector{T}(data)
4056
CircularVector(def::T, size::Int) where T = CircularVector{T}(fill(def, size))
4157

4258
Base.IndexStyle(::Type{<:CircularVector}) = IndexLinear()
4359

44-
end
60+
end

test/runtests.jl

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,85 @@
11
using CircularArrays
2+
using OffsetArrays
23
using Test
34

4-
v1 = CircularVector(rand(Int64, 5))
5-
65
@test IndexStyle(CircularArray) == IndexCartesian()
76
@test IndexStyle(CircularVector) == IndexLinear()
87

9-
@test size(v1, 1) == 5
10-
@test typeof(v1) == CircularVector{Int64}
11-
@test isa(v1, CircularVector)
12-
@test isa(v1, AbstractVector{Int})
13-
@test !isa(v1, AbstractVector{String})
14-
@test v1[2] == v1[2 + length(v1)]
15-
v1[2] = 0
16-
v1[3] = 0
17-
@test v1[2] == v1[3]
18-
@test_throws MethodError v1[2] = "Hello"
19-
20-
v2 = CircularVector("abcde", 5)
21-
22-
@test prod(v2) == "abcde"^5
23-
24-
@test_throws MethodError push!(v1, 15)
25-
26-
b_arr = [2 4 6 8; 10 12 14 16; 18 20 22 24]
27-
a1 = CircularArray(b_arr)
28-
@test size(a1) == (3, 4)
29-
@test a1[2, 3] == 14
30-
a1[2, 3] = 17
31-
@test a1[2, 3] == 17
32-
@test !isa(a1, CircularVector)
33-
@test !isa(a1, AbstractVector)
34-
@test isa(a1, AbstractArray)
35-
36-
@test size(reshape(a1, (2, 2, 3))) == (2, 2, 3)
37-
38-
a2 = CircularArray(4, (2, 3))
39-
@test isa(a2, CircularArray{Int, 2})
8+
@testset "vector" begin
9+
data = rand(Int64, 5)
10+
v1 = CircularVector(data)
11+
12+
@test size(v1, 1) == 5
13+
@test typeof(v1) == CircularVector{Int64}
14+
@test isa(v1, CircularVector)
15+
@test isa(v1, AbstractVector{Int})
16+
@test !isa(v1, AbstractVector{String})
17+
@test v1[2] == v1[2 + length(v1)]
18+
19+
@test v1[0] == data[end]
20+
@test v1[-4:10] == [data; data; data]
21+
@test v1[-3:1][-1] == data[end]
22+
@test v1[[true,false,true,false,true]] == v1[[1,3,0]]
23+
24+
v1copy = copy(v1)
25+
v1_2 = v1[2]
26+
v1[2] = 0
27+
v1[3] = 0
28+
@test v1[2] == v1[3] == 0
29+
@test v1copy[2] == v1_2
30+
@test v1copy[7] == v1_2
31+
@test_throws MethodError v1[2] = "Hello"
32+
33+
v2 = CircularVector("abcde", 5)
34+
35+
@test prod(v2) == "abcde"^5
36+
37+
@test_throws MethodError push!(v1, 15)
38+
end
39+
40+
@testset "matrix" begin
41+
b_arr = [2 4 6 8; 10 12 14 16; 18 20 22 24]
42+
a1 = CircularArray(b_arr)
43+
@test size(a1) == (3, 4)
44+
@test a1[2, 3] == 14
45+
a1[2, 3] = 17
46+
@test a1[2, 3] == 17
47+
@test a1[-1, 7] == 17
48+
@test a1[-1:5, 4:10][1, 4] == 17
49+
@test a1[:, -1:-1][2, 1] == 17
50+
@test !isa(a1, CircularVector)
51+
@test !isa(a1, AbstractVector)
52+
@test isa(a1, AbstractArray)
53+
54+
@test size(reshape(a1, (2, 2, 3))) == (2, 2, 3)
55+
56+
a2 = CircularArray(4, (2, 3))
57+
@test isa(a2, CircularArray{Int, 2})
58+
end
59+
60+
@testset "offset indices" begin
61+
i = OffsetArray(1:5,-3)
62+
a = CircularArray(i)
63+
@test axes(a) == axes(i)
64+
@test a[1] == 4
65+
@test a[10] == a[-10] == a[0] == 3
66+
@test a[-2:7] == [1:5; 1:5]
67+
@test a[0:9] == [3:5; 1:5; 1:2]
68+
@test a[1:10][-10] == 3
69+
@test a[i] == OffsetArray([4,5,1,2,3],-3)
70+
71+
circ_a = circshift(a,3)
72+
@test axes(circ_a) == axes(a)
73+
@test circ_a[1:5] == 1:5
74+
75+
j = OffsetArray([true,false,true],1)
76+
@test a[j] == [5,2]
77+
78+
data = reshape(1:9,3,3)
79+
a = CircularArray(OffsetArray(data,-1,-1))
80+
@test collect(a) == data
81+
@test all(a[x,y] == data[mod1(x+1,3),mod1(y+1,3)] for x=-10:10, y=-10:10)
82+
@test a[i,1] == CircularArray(OffsetArray([5,6,4,5,6],-2:2))
83+
@test a[CartesianIndex.(i,i)] == CircularArray(OffsetArray([5,9,1,5,9],-2:2))
84+
@test a[a .> 4] == 5:9
85+
end

0 commit comments

Comments
 (0)