Skip to content

Commit 3fc46cd

Browse files
authored
Add append!/push!/pushfirst!/pop!/popfirst! for BlockVector (#116)
* Add append!/push!/pushfirst!/pop!/popfirst! for BlockVector * Rename append_nocopy! to blockappend!, clarify its behavior * Fix a typo in test * Add new API blockappend! for possibly-non-copying append! * Add blockpush! * Add block{pushfirst!,pop!,popfirst!}
1 parent 5ae130c commit 3fc46cd

File tree

7 files changed

+555
-0
lines changed

7 files changed

+555
-0
lines changed

docs/src/lib/public.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ BlockArray
5050
undef_blocks
5151
UndefBlocksInitializer
5252
mortar
53+
blockappend!
54+
blockpush!
55+
blockpushfirst!
56+
blockpop!
57+
blockpopfirst!
58+
Base.append!
59+
Base.push!
60+
Base.pushfirst!
61+
Base.pop!
62+
Base.popfirst!
5363
```
5464

5565

src/BlockArrays.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export undef_blocks, undef, findblock, findblockindex
1616

1717
export khatri_rao
1818

19+
export blockappend!, blockpush!, blockpushfirst!, blockpop!, blockpopfirst!
20+
1921
import Base: @propagate_inbounds, Array, to_indices, to_index,
2022
unsafe_indices, first, last, size, length, unsafe_length,
2123
unsafe_convert,
@@ -48,5 +50,7 @@ include("blockarrayinterface.jl")
4850
include("blockbroadcast.jl")
4951
include("blocklinalg.jl")
5052
include("blockproduct.jl")
53+
include("blockreduce.jl")
54+
include("blockdeque.jl")
5155

5256
end # module

src/blockdeque.jl

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
"""
2+
blockappend!(dest::BlockVector, sources...) -> dest
3+
4+
Append blocks from `sources` to `dest`. The number of blocks in `dest` are
5+
increased by `sum(blocklength, sources)`.
6+
7+
This function avoids copying the elements of the blocks in `sources` when
8+
these blocks are compatible with `dest`. Importantly, this means that
9+
mutating `sources` afterwards alters the items in `dest` and it may even
10+
break the invariance of `dest` if the length of `sources` are changed.
11+
12+
The blocks in `dest` must not alias with `sources` or components of them.
13+
For example, the result of `blockappend!(x, x)` is undefined.
14+
15+
# Examples
16+
```jldoctest
17+
julia> using BlockArrays
18+
19+
julia> blockappend!(mortar([[1], [2, 3]]), mortar([[4, 5]]))
20+
3-blocked 5-element BlockArray{Int64,1}:
21+
1
22+
23+
2
24+
3
25+
26+
4
27+
5
28+
```
29+
"""
30+
blockappend!(dest::BlockVector, s1, s2, sources...) =
31+
foldl(blockappend!, (s1, s2, sources...); init = dest)
32+
33+
function blockappend!(dest::BlockVector{<:Any,T}, src::BlockVector{<:Any,T}) where {T}
34+
append!(dest.blocks, src.blocks)
35+
offset = last(dest.axes[1]) + 1 - src.axes[1].first
36+
append!(dest.axes[1].lasts, (n + offset for n in src.axes[1].lasts))
37+
return dest
38+
end
39+
40+
function blockappend!(
41+
dest::BlockVector{<:Any,<:AbstractArray{T}},
42+
src::PseudoBlockVector{<:Any,T},
43+
) where {T}
44+
if blocklength(src) == 1
45+
return _blockpush!(dest, src.blocks)
46+
else
47+
return blockappend_fallback!(dest, src)
48+
end
49+
end
50+
51+
blockappend!(
52+
dest::BlockVector{<:Any,<:AbstractArray{T}},
53+
src::T,
54+
) where {T<:AbstractVector} = _blockpush!(dest, src)
55+
56+
blockappend!(dest::BlockVector{<:Any,<:Any}, src::AbstractVector) =
57+
blockappend_fallback!(dest, src)
58+
59+
blockappend_fallback!(dest::BlockVector{<:Any,<:AbstractArray{T}}, src) where {T} =
60+
blockappend!(dest, mortar([convert(T, @view src[b]) for b in blockaxes(src, 1)]))
61+
62+
"""
63+
blockpush!(dest::BlockVector, blocks...) -> dest
64+
65+
Push `blocks` to the end of `dest`.
66+
67+
This function avoids copying the elements of the `blocks` when these blocks
68+
are compatible with `dest`. Importantly, this means that mutating `blocks`
69+
afterwards alters the items in `dest` and it may even break the invariance
70+
of `dest` if the length of `blocks` are changed.
71+
72+
# Examples
73+
```jldoctest
74+
julia> using BlockArrays
75+
76+
julia> blockpush!(mortar([[1], [2, 3]]), [4, 5], [6])
77+
4-blocked 6-element BlockArray{Int64,1}:
78+
1
79+
80+
2
81+
3
82+
83+
4
84+
5
85+
86+
6
87+
```
88+
"""
89+
blockpush!(dest::BlockVector, blocks...) = foldl(blockpush!, blocks; init = dest)
90+
91+
blockpush!(dest::BlockVector{<:Any,<:AbstractArray{T}}, block::T) where {T} =
92+
_blockpush!(dest, block)
93+
94+
blockpush!(dest::BlockVector, block) = _blockpush!(dest, _newblockfor(dest, block))
95+
96+
_newblockfor(dest, block) =
97+
if Iterators.IteratorSize(block) isa Union{Base.HasShape,Base.HasLength}
98+
copyto!(eltype(dest.blocks)(undef, length(block)), block)
99+
else
100+
foldl(push!, block; init = eltype(dest.blocks)(undef, 0))
101+
end
102+
103+
function _blockpush!(dest, block)
104+
push!(dest.blocks, block)
105+
push!(dest.axes[1].lasts, last(dest.axes[1]) + length(block))
106+
return dest
107+
end
108+
109+
"""
110+
blockpushfirst!(dest::BlockVector, blocks...) -> dest
111+
112+
Push `blocks` to the beginning of `dest`. See also [`blockpush!`](@ref).
113+
114+
This function avoids copying the elements of the `blocks` when these blocks
115+
are compatible with `dest`. Importantly, this means that mutating `blocks`
116+
afterwards alters the items in `dest` and it may even break the invariance
117+
of `dest` if the length of `blocks` are changed.
118+
119+
# Examples
120+
```jldoctest
121+
julia> using BlockArrays
122+
123+
julia> blockpushfirst!(mortar([[1], [2, 3]]), [4, 5], [6])
124+
4-blocked 6-element BlockArray{Int64,1}:
125+
4
126+
5
127+
128+
6
129+
130+
1
131+
132+
2
133+
3
134+
```
135+
"""
136+
blockpushfirst!(A::BlockVector, b1, b2, blocks...) =
137+
foldl(blockpushfirst!, reverse((b1, b2, blocks...)); init = A)
138+
139+
blockpushfirst!(dest::BlockVector{<:Any,<:AbstractArray{T}}, block::T) where {T} =
140+
_blockpushfirst!(dest, block)
141+
142+
blockpushfirst!(dest::BlockVector{<:Any,<:Any}, block) =
143+
_blockpushfirst!(dest, _newblockfor(dest, block))
144+
145+
function _blockpushfirst!(dest, block)
146+
pushfirst!(dest.blocks, block)
147+
dest.axes[1].lasts .+= length(block) - 1 + dest.axes[1].first
148+
pushfirst!(dest.axes[1].lasts, length(block))
149+
return dest
150+
end
151+
152+
"""
153+
blockpop!(A::BlockVector) -> block
154+
155+
Pop a `block` from the end of `dest`.
156+
157+
# Examples
158+
```jldoctest
159+
julia> using BlockArrays
160+
161+
julia> A = mortar([[1], [2, 3]]);
162+
163+
julia> blockpop!(A)
164+
2-element Array{Int64,1}:
165+
2
166+
3
167+
168+
julia> A
169+
1-blocked 1-element BlockArray{Int64,1}:
170+
1
171+
```
172+
"""
173+
function blockpop!(A::BlockVector)
174+
block = pop!(A.blocks)
175+
pop!(A.axes[1].lasts)
176+
return block
177+
end
178+
179+
"""
180+
blockpopfirst!(dest::BlockVector) -> block
181+
182+
Pop a `block` from the beginning of `dest`.
183+
184+
# Examples
185+
```jldoctest
186+
julia> using BlockArrays
187+
188+
julia> A = mortar([[1], [2, 3]]);
189+
190+
julia> blockpopfirst!(A)
191+
1-element Array{Int64,1}:
192+
1
193+
194+
julia> A
195+
1-blocked 2-element BlockArray{Int64,1}:
196+
2
197+
3
198+
```
199+
"""
200+
function blockpopfirst!(A::BlockVector)
201+
block = popfirst!(A.blocks)
202+
n = popfirst!(A.axes[1].lasts)
203+
A.axes[1].lasts .-= n
204+
return block
205+
end
206+
207+
"""
208+
append!(dest::BlockVector, sources...)
209+
210+
Append items from `sources` to the last block of `dest`.
211+
212+
The blocks in `dest` must not alias with `sources` or components of them.
213+
For example, the result of `append!(x, x)` is undefined.
214+
215+
# Examples
216+
```jldoctest
217+
julia> using BlockArrays
218+
219+
julia> append!(mortar([[1], [2, 3]]), mortar([[4], [5]]))
220+
2-blocked 5-element BlockArray{Int64,1}:
221+
1
222+
223+
2
224+
3
225+
4
226+
5
227+
```
228+
"""
229+
Base.append!(dest::BlockVector, sources...) = foldl(append!, sources; init = dest)
230+
231+
Base.append!(dest::BlockVector, src) = append_itr!(dest, Base.IteratorSize(src), src)
232+
233+
function append_itr!(dest::BlockVector, ::Union{Base.HasShape,Base.HasLength}, src)
234+
block = dest.blocks[end]
235+
li = lastindex(block)
236+
resize!(block, length(block) + length(src))
237+
# Equivalent to `i = li; for x in src; ...; end` but (maybe) faster:
238+
foldl(src, init = li) do i, x
239+
Base.@_inline_meta
240+
i += 1
241+
@inbounds block[i] = x
242+
return i
243+
end
244+
da, = dest.axes
245+
da.lasts[end] += length(src)
246+
return dest
247+
end
248+
249+
function append_itr!(dest::BlockVector, ::Base.SizeUnknown, src)
250+
block = dest.blocks[end]
251+
# Equivalent to `n = 0; for x in src; ...; end` but (maybe) faster:
252+
n = foldl(src, init = 0) do n, x
253+
push!(block, x)
254+
return n + 1
255+
end
256+
da, = dest.axes
257+
da.lasts[end] += n
258+
return dest
259+
end
260+
261+
# remove empty blocks at the end
262+
function _squash_lasts!(A::BlockVector)
263+
while !isempty(A.blocks) && isempty(A.blocks[end])
264+
pop!(A.blocks)
265+
pop!(A.axes[1].lasts)
266+
end
267+
end
268+
269+
# remove empty blocks at the beginning
270+
function _squash_firsts!(A::BlockVector)
271+
while !isempty(A.blocks) && isempty(A.blocks[1])
272+
popfirst!(A.blocks)
273+
popfirst!(A.axes[1].lasts)
274+
end
275+
end
276+
277+
"""
278+
pop!(A::BlockVector)
279+
280+
Pop the last element from the last non-empty block. Remove all empty
281+
blocks at the end.
282+
"""
283+
function Base.pop!(A::BlockVector)
284+
isempty(A) && throw(Argument("array must be nonempty"))
285+
_squash_lasts!(A)
286+
x = pop!(A.blocks[end])
287+
lasts = A.axes[1].lasts
288+
if isempty(A.blocks[end])
289+
pop!(A.blocks)
290+
pop!(lasts)
291+
else
292+
lasts[end] -= 1
293+
end
294+
return x
295+
end
296+
297+
"""
298+
popfirst!(A::BlockVector)
299+
300+
Pop the first element from the first non-empty block. Remove all empty
301+
blocks at the beginning.
302+
"""
303+
function Base.popfirst!(A::BlockVector)
304+
isempty(A) && throw(Argument("array must be nonempty"))
305+
_squash_firsts!(A)
306+
x = popfirst!(A.blocks[1])
307+
ax, = A.axes
308+
if isempty(A.blocks[1])
309+
popfirst!(A.blocks)
310+
popfirst!(ax.lasts)
311+
else
312+
ax.lasts[1] -= 1
313+
end
314+
return x
315+
end
316+
317+
"""
318+
push!(dest::BlockVector, items...)
319+
320+
Push items to the end of the last block.
321+
"""
322+
Base.push!(dest::BlockVector, items...) = append!(dest, items)
323+
324+
"""
325+
pushfirst!(A::BlockVector, items...)
326+
327+
Push items to the beginning of the first block.
328+
"""
329+
function Base.pushfirst!(A::BlockVector, items...)
330+
pushfirst!(A.blocks[1], items...)
331+
A.axes[1].lasts .+= length(items)
332+
return A
333+
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

0 commit comments

Comments
 (0)