Skip to content

Commit 3cd7994

Browse files
committed
init
1 parent 10927af commit 3cd7994

File tree

3 files changed

+276
-2
lines changed

3 files changed

+276
-2
lines changed

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,77 @@
11
# CircularArrayBuffers
22

33
[![Build Status](https://github.com/JuliaReinforcementLearning/CircularArrayBuffers.jl/workflows/CI/badge.svg)](https://github.com/JuliaReinforcementLearning/CircularArrayBuffers.jl/actions)
4+
5+
`CircularArrayBuffers.jl` is a small package to wrap an `AbstractArray` as a buffer along the last dimension. The main benefit compared to [`CircularBuffer`](https://juliacollections.github.io/DataStructures.jl/latest/circ_buffer/) in [`DataStructures.jl`](https://github.com/JuliaCollections/DataStructures.jl) is that the view of consecutive elements is a `SubArray`.
6+
7+
## Usage
8+
9+
```julia
10+
julia> using CircularArrayBuffers
11+
12+
julia> names(CircularArrayBuffers)
13+
5-element Array{Symbol,1}:
14+
:CircularArrayBuffer
15+
:CircularArrayBuffers
16+
:CircularVectorBuffer
17+
:capacity
18+
:isfull
19+
20+
julia> a = CircularArrayBuffer(rand(2,3))
21+
2×3 CircularArrayBuffer{Float64,2}:
22+
0.0510714 0.0260738 0.0245707
23+
0.856257 0.571643 0.0189365
24+
25+
julia> b = CircularArrayBuffer{Float64}(2,3)
26+
2×0 CircularArrayBuffer{Float64,2}
27+
28+
julia> push!(b, rand(2))
29+
2×1 CircularArrayBuffer{Float64,2}:
30+
0.4215856115651755
31+
0.5485806794787502
32+
33+
julia> push!(b, rand(2))
34+
2×2 CircularArrayBuffer{Float64,2}:
35+
0.421586 0.640501
36+
0.548581 0.774729
37+
38+
julia> push!(b, rand(2))
39+
2×3 CircularArrayBuffer{Float64,2}:
40+
0.421586 0.640501 0.653054
41+
0.548581 0.774729 0.902611
42+
43+
julia> push!(b, rand(2))
44+
2×3 CircularArrayBuffer{Float64,2}:
45+
0.640501 0.653054 0.640373
46+
0.774729 0.902611 0.227435
47+
48+
julia> pop!(b)
49+
2-element view(::CircularArrayBuffer{Float64,2}, :, 3) with eltype Float64:
50+
0.6403725468830439
51+
0.22743495787074597
52+
53+
julia> b
54+
2×2 CircularArrayBuffer{Float64,2}:
55+
0.640501 0.653054
56+
0.774729 0.902611
57+
58+
julia> size(b)
59+
(2, 2)
60+
61+
julia> capacity(b)
62+
3
63+
64+
julia> isfull(b)
65+
false
66+
67+
julia> push!(b, rand(2))
68+
2×3 CircularArrayBuffer{Float64,2}:
69+
0.640501 0.653054 0.885887
70+
0.774729 0.902611 0.0332439
71+
72+
julia> isfull(b)
73+
true
74+
75+
julia> eltype(b)
76+
Float64
77+
```

src/CircularArrayBuffers.jl

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,82 @@
11
module CircularArrayBuffers
22

3-
# Write your package code here.
3+
export CircularArrayBuffer, CircularVectorBuffer, capacity, isfull
4+
5+
"""
6+
CircularArrayBuffer{T}(sz::Integer...) -> CircularArrayBuffer{T, N}
7+
8+
`CircularArrayBuffer` uses a `N`-dimension `Array` of size `sz` to serve as a buffer for
9+
`N-1`-dimension `Array`s of the same size.
10+
"""
11+
mutable struct CircularArrayBuffer{T,N} <: AbstractArray{T,N}
12+
buffer::Array{T,N}
13+
first::Int
14+
nframes::Int
15+
step_size::Int
16+
end
17+
18+
const CircularVectorBuffer{T} = CircularArrayBuffer{T, 1}
19+
20+
function CircularArrayBuffer{T}(d::Integer...) where {T}
21+
N = length(d)
22+
CircularArrayBuffer(Array{T}(undef, d...), 1, 0, N == 1 ? 1 : *(d[1:end-1]...))
23+
end
24+
25+
function CircularArrayBuffer(A::AbstractArray{T,N}) where {T,N}
26+
CircularArrayBuffer(A, 1, size(A, N), N == 1 ? 1 : *(size(A)[1:end-1]...))
27+
end
28+
29+
Base.IndexStyle(::CircularArrayBuffer) = IndexLinear()
30+
31+
Base.size(cb::CircularArrayBuffer{T,N}, i::Integer) where {T,N} = i == N ? cb.nframes : size(cb.buffer, i)
32+
Base.size(cb::CircularArrayBuffer{T,N}) where {T,N} = ntuple(i -> size(cb, i), N)
33+
Base.getindex(cb::CircularArrayBuffer{T,N}, i::Int) where {T,N} = getindex(cb.buffer, _buffer_index(cb, i))
34+
Base.setindex!(cb::CircularArrayBuffer{T,N}, v, i::Int) where {T,N} = setindex!(cb.buffer, v, _buffer_index(cb, i))
35+
36+
capacity(cb::CircularArrayBuffer{T,N}) where {T,N} = size(cb.buffer, N)
37+
isfull(cb::CircularArrayBuffer) = cb.nframes == capacity(cb)
38+
Base.isempty(cb::CircularArrayBuffer) = cb.nframes == 0
39+
40+
@inline function _buffer_index(cb::CircularArrayBuffer, i::Int)
41+
ind = (cb.first - 1) * cb.step_size + i
42+
mod1(ind, length(cb.buffer))
43+
end
44+
45+
@inline function _buffer_frame(cb::CircularArrayBuffer, i::Int)
46+
n = capacity(cb)
47+
idx = cb.first + i - 1
48+
mod1(idx, n)
49+
end
50+
51+
_buffer_frame(cb::CircularArrayBuffer, I::Vector{Int}) = map(i -> _buffer_frame(cb, i), I)
52+
53+
function Base.empty!(cb::CircularArrayBuffer)
54+
cb.nframes = 0
55+
cb
56+
end
57+
58+
function Base.push!(cb::CircularArrayBuffer{T, N}, data) where {T,N}
59+
if cb.nframes == capacity(cb)
60+
cb.first = (cb.first == capacity(cb) ? 1 : cb.first + 1)
61+
else
62+
cb.nframes += 1
63+
end
64+
if N == 1
65+
cb[cb.nframes] = data
66+
else
67+
cb[ntuple(_ -> (:), N - 1)..., cb.nframes] .= data
68+
end
69+
cb
70+
end
71+
72+
function Base.pop!(cb::CircularArrayBuffer{T, N}) where {T,N}
73+
if cb.nframes <= 0
74+
throw(ArgumentError("buffer must be non-empty"))
75+
else
76+
res = @views cb[ntuple(_ -> (:), N - 1)..., cb.nframes]
77+
cb.nframes -= 1
78+
res
79+
end
80+
end
481

582
end

test/runtests.jl

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,128 @@ using CircularArrayBuffers
22
using Test
33

44
@testset "CircularArrayBuffers.jl" begin
5-
# Write your tests here.
5+
A = ones(2, 2)
6+
C = ones(Float32, 2, 2)
7+
@testset "1D Int" begin
8+
b = CircularArrayBuffer{Int}(3)
9+
10+
@test eltype(b) == Int
11+
@test capacity(b) == 3
12+
@test isfull(b) == false
13+
@test isempty(b) == true
14+
@test length(b) == 0
15+
@test size(b) == (0,)
16+
# element must has the exact same length with the element of buffer
17+
@test_throws Exception push!(b, [1, 2])
18+
19+
for x in 1:3
20+
push!(b, x)
21+
end
22+
23+
@test capacity(b) == 3
24+
@test isfull(b) == true
25+
@test length(b) == 3
26+
@test size(b) == (3,)
27+
@test b[1] == 1
28+
@test b[end] == 3
29+
@test b[1:end] == [1, 2, 3]
30+
31+
for x in 4:5
32+
push!(b, x)
33+
end
34+
35+
@test capacity(b) == 3
36+
@test length(b) == 3
37+
@test size(b) == (3,)
38+
@test b[1] == 3
39+
@test b[end] == 5
40+
@test b[1:end] == [3, 4, 5]
41+
42+
empty!(b)
43+
@test isfull(b) == false
44+
@test isempty(b) == true
45+
@test length(b) == 0
46+
@test size(b) == (0,)
47+
48+
push!(b, 6)
49+
@test isfull(b) == false
50+
@test isempty(b) == false
51+
@test length(b) == 1
52+
@test size(b) == (1,)
53+
@test b[1] == 6
54+
55+
push!(b, 7)
56+
push!(b, 8)
57+
@test isfull(b) == true
58+
@test isempty(b) == false
59+
@test length(b) == 3
60+
@test size(b) == (3,)
61+
@test b[[1, 2, 3]] == [6, 7, 8]
62+
63+
push!(b, 9)
64+
@test isfull(b) == true
65+
@test isempty(b) == false
66+
@test length(b) == 3
67+
@test size(b) == (3,)
68+
@test b[[1, 2, 3]] == [7, 8, 9]
69+
70+
x = pop!(b)
71+
@test x == 9
72+
@test length(b) == 2
73+
@test b[[1, 2]] == [7, 8]
74+
75+
x = pop!(b)
76+
@test x == 8
77+
@test length(b) == 1
78+
@test b[1] == 7
79+
80+
x = pop!(b)
81+
@test x == 7
82+
@test length(b) == 0
83+
84+
@test_throws ArgumentError pop!(b)
85+
end
86+
87+
@testset "2D Float64" begin
88+
b = CircularArrayBuffer{Float64}(2, 2, 3)
89+
90+
@test eltype(b) == Float64
91+
@test capacity(b) == 3
92+
@test isfull(b) == false
93+
@test length(b) == 0
94+
@test size(b) == (2, 2, 0)
95+
96+
for x in 1:3
97+
push!(b, x * A)
98+
end
99+
100+
@test capacity(b) == 3
101+
@test isfull(b) == true
102+
@test length(b) == 2 * 2 * 3
103+
@test size(b) == (2, 2, 3)
104+
for i in 1:3
105+
@test b[:, :, i] == i * A
106+
end
107+
@test b[:, :, end] == 3 * A
108+
109+
for x in 4:5
110+
push!(b, x * ones(2, 2))
111+
end
112+
113+
@test capacity(b) == 3
114+
@test length(b) == 2 * 2 * 3
115+
@test size(b) == (2, 2, 3)
116+
@test b[:, :, 1] == 3 * A
117+
@test b[:, :, end] == 5 * A
118+
119+
@test b == reshape([c for x in 3:5 for c in x * A], 2, 2, 3)
120+
121+
push!(b, 6 * ones(Float32, 2, 2))
122+
push!(b, 7 * ones(Int, 2, 2))
123+
@test b == reshape([c for x in 5:7 for c in x * A], 2, 2, 3)
124+
125+
x = pop!(b)
126+
@test x == 7 * ones(2,2)
127+
@test b == reshape([c for x in 5:6 for c in x * A], 2, 2, 2)
128+
end
6129
end

0 commit comments

Comments
 (0)