Skip to content

Commit 2ad7c61

Browse files
committed
Add append!/push!/pushfirst!/pop!/popfirst! for BlockVector
1 parent 2424ea1 commit 2ad7c61

File tree

7 files changed

+276
-0
lines changed

7 files changed

+276
-0
lines changed

docs/src/lib/public.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ BlockArray
5050
undef_blocks
5151
UndefBlocksInitializer
5252
mortar
53+
Base.append!
54+
Base.push!
55+
Base.pushfirst!
56+
Base.pop!
57+
Base.popfirst!
5358
```
5459

5560

src/BlockArrays.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,7 @@ include("blockarrayinterface.jl")
4848
include("blockbroadcast.jl")
4949
include("blocklinalg.jl")
5050
include("blockproduct.jl")
51+
include("blockreduce.jl")
52+
include("blockdeque.jl")
5153

5254
end # module

src/blockdeque.jl

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
append!(dest::BlockVector, sources...; alias = false)
3+
4+
Append items from `sources` to the last block of `dest`. If `alias = true`,
5+
append the blocks in `sources` as-is if they are compatible with the internal
6+
block array type of `dest`. Importantly, this means that mutating `sources`
7+
afterwards alters the items in `dest` and it may even break the invariance
8+
of `dest` if the length of `sources` are changed. The elements may be copied
9+
even if `alias = true` if the corresponding implementation does not exist.
10+
11+
The blocks in `dest` must not alias with `sources` or components of them.
12+
For example, the result of `append!(x, x)` is undefined.
13+
"""
14+
Base.append!(dest::BlockVector, sources...; alias::Bool = false) =
15+
foldl((d, s) -> append!(d, s; alias = alias), sources; init = dest)
16+
17+
function Base.append!(dest::BlockVector, src; alias::Bool = false)
18+
if _blocktype(dest) === _blocktype(src) && alias
19+
return append_nocopy!(dest, src)
20+
else
21+
return append_copy!(dest, src)
22+
end
23+
end
24+
25+
_blocktype(::Any) = nothing
26+
_blocktype(::T) where {T<:AbstractArray} = T
27+
_blocktype(::BlockArray{<:Any,<:Any,<:AbstractArray{T}}) where {T<:AbstractArray} = T
28+
_blocktype(::PseudoBlockArray{<:Any,<:Any,T}) where {T<:AbstractArray} = T
29+
30+
function append_nocopy!(dest::BlockVector{<:Any,T}, src::BlockVector{<:Any,T}) where {T}
31+
isempty(src) && return dest
32+
append!(dest.blocks, src.blocks)
33+
offset = last(dest.axes[1]) + 1 - src.axes[1].first
34+
append!(dest.axes[1].lasts, (n + offset for n in src.axes[1].lasts))
35+
return dest
36+
end
37+
38+
append_nocopy!(
39+
dest::BlockVector{<:Any,<:AbstractArray{T}},
40+
src::PseudoBlockVector{<:Any,T},
41+
) where {T} = append_nocopy!(dest, src.blocks)
42+
43+
function append_nocopy!(dest::BlockVector{<:Any,<:AbstractArray{T}}, src::T) where {T}
44+
isempty(src) && return dest
45+
push!(dest.blocks, src)
46+
push!(dest.axes[1].lasts, last(dest.axes[1]) + length(src))
47+
return dest
48+
end
49+
50+
append_copy!(dest::BlockVector, src) = _append_copy!(dest, Base.IteratorSize(src), src)
51+
52+
function _append_copy!(dest::BlockVector, ::Union{Base.HasShape,Base.HasLength}, src)
53+
block = dest.blocks[end]
54+
li = lastindex(block)
55+
resize!(block, length(block) + length(src))
56+
# Equivalent to `i = li; for x in src; ...; end` but (maybe) faster:
57+
foldl(src, init = li) do i, x
58+
Base.@_inline_meta
59+
i += 1
60+
@inbounds block[i] = x
61+
return i
62+
end
63+
da, = dest.axes
64+
da.lasts[end] += length(src)
65+
return dest
66+
end
67+
68+
function _append_copy!(dest::BlockVector, ::Base.SizeUnknown, src)
69+
block = dest.blocks[end]
70+
# Equivalent to `n = 0; for x in src; ...; end` but (maybe) faster:
71+
n = foldl(src, init = 0) do n, x
72+
push!(block, x)
73+
return n + 1
74+
end
75+
da, = dest.axes
76+
da.lasts[end] += n
77+
return dest
78+
end
79+
80+
# remove empty blocks at the end
81+
function _squash_lasts!(A::BlockVector)
82+
while !isempty(A.blocks) && isempty(A.blocks[end])
83+
pop!(A.blocks)
84+
pop!(A.axes[1].lasts)
85+
end
86+
end
87+
88+
# remove empty blocks at the beginning
89+
function _squash_firsts!(A::BlockVector)
90+
while !isempty(A.blocks) && isempty(A.blocks[1])
91+
popfirst!(A.blocks)
92+
popfirst!(A.axes[1].lasts)
93+
end
94+
end
95+
96+
"""
97+
pop!(A::BlockVector)
98+
99+
Pop the last element from the last non-empty block. Remove all empty
100+
blocks at the end.
101+
"""
102+
function Base.pop!(A::BlockVector)
103+
isempty(A) && throw(Argument("array must be nonempty"))
104+
_squash_lasts!(A)
105+
x = pop!(A.blocks[end])
106+
lasts = A.axes[1].lasts
107+
if isempty(A.blocks[end])
108+
pop!(A.blocks)
109+
pop!(lasts)
110+
else
111+
lasts[end] -= 1
112+
end
113+
return x
114+
end
115+
116+
"""
117+
popfirst!(A::BlockVector)
118+
119+
Pop the first element from the first non-empty block. Remove all empty
120+
blocks at the beginning.
121+
"""
122+
function Base.popfirst!(A::BlockVector)
123+
isempty(A) && throw(Argument("array must be nonempty"))
124+
_squash_firsts!(A)
125+
x = popfirst!(A.blocks[1])
126+
ax, = A.axes
127+
if isempty(A.blocks[1])
128+
popfirst!(A.blocks)
129+
popfirst!(ax.lasts)
130+
else
131+
ax.lasts[1] -= 1
132+
end
133+
return x
134+
end
135+
136+
"""
137+
push!(dest::BlockVector, items...)
138+
139+
Push items to the end of the last block.
140+
"""
141+
Base.push!(dest::BlockVector, items...) = append!(dest, items)
142+
143+
"""
144+
pushfirst!(A::BlockVector, items...)
145+
146+
Push items to the beginning of the first block.
147+
"""
148+
function Base.pushfirst!(A::BlockVector, items...)
149+
pushfirst!(A.blocks[1], items...)
150+
A.axes[1].lasts .+= length(items)
151+
return A
152+
end

src/blockreduce.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# * Those seemingly no-op `where {F, OP}` for forcing specialization.
2+
# See: https://github.com/JuliaLang/julia/pull/33917
3+
# * Per-block reduction strategy is correct only for vectors.
4+
5+
# Let mapping transducer in `Base` compose an efficient nested loop:
6+
Base.mapfoldl(f::F, op::OP, B::BlockVector; kw...) where {F, OP} =
7+
foldl(op, (f(x) for block in B.blocks for x in block); kw...)
8+
9+
Base.mapreduce(f::F, op::OP, B::BlockVector; kw...) where {F, OP} =
10+
mapfoldl(op, B.blocks; kw...) do block
11+
mapreduce(f, op, block; kw...)
12+
end
13+
14+
Base.mapfoldl(f::F, op::OP, B::PseudoBlockArray; kw...) where {F, OP} =
15+
mapfoldl(f, op, B.blocks; kw...)
16+
17+
Base.mapreduce(f::F, op::OP, B::PseudoBlockArray; kw...) where {F, OP} =
18+
mapreduce(f, op, B.blocks; kw...)

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ using BlockArrays, LinearAlgebra, Test
99
include("test_blockbroadcast.jl")
1010
include("test_blocklinalg.jl")
1111
include("test_blockproduct.jl")
12+
include("test_blockreduce.jl")
13+
include("test_blockdeque.jl")
1214
end

test/test_blockdeque.jl

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using BlockArrays, Test
2+
3+
@testset "append!(::BlockVector, vector)" begin
4+
@testset for alias in [false, true],
5+
compatible in [false, true],
6+
srctype in [:BlockVector, :PseudoBlockVector, :Vector]
7+
8+
dest = mortar([[1, 2, 3], [4, 5]])
9+
10+
# Create `src` array:
11+
if compatible
12+
T = Int
13+
else
14+
T = Float64
15+
end
16+
if srctype === :BlockVector
17+
src = mortar([T[6, 7], T[8, 9]])
18+
elseif srctype === :PseudoBlockVector
19+
src = PseudoBlockVector(T[6:9;], [3, 1])
20+
elseif srctype === :Vector
21+
src = T[6:9;]
22+
else
23+
error("Unknown srctype = $srctype")
24+
end
25+
26+
@test append!(dest, src; alias = alias) === dest
27+
@test dest == 1:9
28+
src[1] = 666
29+
if alias && compatible
30+
@test dest[6] == 666
31+
else
32+
@test dest[6] == 6
33+
end
34+
end
35+
end
36+
37+
@testset "append!(::BlockVector, iterator)" begin
38+
@testset "$label" for (label, itr) in [
39+
"with length" => (x + 0 for x in 6:9),
40+
"no length" => (x for x in 6:9 if x > 0),
41+
]
42+
dest = mortar([[1, 2, 3], [4, 5]])
43+
@test append!(dest, src) === dest
44+
@test dest == 1:9
45+
end
46+
end
47+
48+
@testset "push!" begin
49+
A = mortar([[1, 2, 3], [4, 5]])
50+
push!(A, 6)
51+
push!(A, 7, 8, 9)
52+
@test A == 1:9
53+
end
54+
55+
@testset "pushfirst!" begin
56+
A = mortar([[1, 2, 3], [4, 5]])
57+
pushfirst!(A, 0)
58+
pushfirst!(A, -3, -2, -1)
59+
@test A == -3:5
60+
end
61+
62+
@testset "pop!" begin
63+
A = mortar([[1, 2, 3], [4, 5]])
64+
B = []
65+
while !isempty(A)
66+
push!(B, pop!(A))
67+
end
68+
@test A == []
69+
@test B == 5:-1:1
70+
end
71+
72+
@testset "popfirst!" begin
73+
A = mortar([[1, 2, 3], [4, 5]])
74+
B = []
75+
while !isempty(A)
76+
push!(B, popfirst!(A))
77+
end
78+
@test A == []
79+
@test B == 1:5
80+
end

test/test_blockreduce.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using BlockArrays, Test
2+
3+
@testset "foldl" begin
4+
x = mortar([rand(3), rand(2)])
5+
@test foldl(push!, x; init = []) == collect(x)
6+
7+
x = PseudoBlockVector(rand(3), [1, 2])
8+
@test foldl(push!, x; init = []) == collect(x)
9+
end
10+
11+
@testset "reduce" begin
12+
x = mortar([rand(Int, 3), rand(Int, 2)])
13+
@test reduce(+, x) == sum(collect(x))
14+
15+
x = PseudoBlockVector(rand(Int, 3), [1, 2])
16+
@test reduce(+, x) == sum(collect(x))
17+
end

0 commit comments

Comments
 (0)