Skip to content

Commit c9961a9

Browse files
authored
Merge pull request #954 from Drvi/td-heap-boundchecks
Improve bounds checks in heap operations
2 parents 35d87fe + e1651ad commit c9961a9

File tree

5 files changed

+48
-29
lines changed

5 files changed

+48
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ Starting from version 0.19.0, CHANGELOG.md is managed in a format that follows <
66
[Unreleased]: https://github.com/JuliaCollections/DataStructures.jl/compare/v0.19.0...HEAD
77
[0.19.0]: https://github.com/JuliaCollections/DataStructures.jl/compare/v0.18.22...v0.19.0
88
[0.19.1]: https://github.com/JuliaCollections/DataStructures.jl/compare/v0.19.0...v0.19.1
9+
[0.19.2]: https://github.com/JuliaCollections/DataStructures.jl/compare/v0.19.1...v0.19.2
910
<!-- links end -->
1011

12+
[0.19.2]
13+
=====================
14+
15+
## Added
16+
17+
- `percolate_up!`, `percolate_down!` now use `@propagate_inbounds` to control bounds checking (JuliaCollections/DataStructures.jl#954)
18+
1119
[0.19.1] - 2025-08-26
1220
=====================
1321

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "DataStructures"
22
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
3-
version = "0.19.1"
3+
version = "0.19.2"
44

55
[deps]
66
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"

src/heaps.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ end
109109
return an array of the first `n` values of `arr` sorted by `ord`.
110110
"""
111111
function nextreme(ord::Base.Ordering, n::Int, arr::AbstractVector{T}) where T
112+
Base.require_one_based_indexing(arr)
112113
if n <= 0
113114
return T[] # sort(arr)[1:n] returns [] for n <= 0
114115
elseif n >= length(arr)
@@ -117,10 +118,10 @@ function nextreme(ord::Base.Ordering, n::Int, arr::AbstractVector{T}) where T
117118

118119
rev = Base.ReverseOrdering(ord)
119120

120-
buffer = heapify(arr[1:n], rev)
121+
buffer = heapify!(arr[1:n], rev)
121122

122-
for i = n + 1 : length(arr)
123-
@inbounds xi = arr[i]
123+
@inbounds for i = n + 1 : length(arr)
124+
xi = arr[i]
124125
if Base.lt(rev, buffer[1], xi)
125126
buffer[1] = xi
126127
percolate_down!(buffer, 1, rev)

src/heaps/arrays_as_heaps.jl

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,35 @@ heapleft(i::Integer) = 2i
1313
heapright(i::Integer) = 2i + 1
1414
heapparent(i::Integer) = div(i, 2)
1515

16-
1716
# Binary min-heap percolate down.
18-
function percolate_down!(xs::AbstractArray, i::Integer, x=xs[i], o::Ordering=Forward, len::Integer=length(xs))
17+
Base.@propagate_inbounds function percolate_down!(xs::AbstractArray, i::Integer, x, o::Ordering=Forward, len::Integer=length(xs))
18+
@boundscheck checkbounds(xs, i)
19+
@boundscheck checkbounds(xs, len)
20+
1921
@inbounds while (l = heapleft(i)) <= len
2022
r = heapright(i)
2123
j = r > len || lt(o, xs[l], xs[r]) ? l : r
2224
lt(o, xs[j], x) || break
2325
xs[i] = xs[j]
2426
i = j
2527
end
26-
xs[i] = x
28+
@inbounds xs[i] = x
2729
end
28-
29-
percolate_down!(xs::AbstractArray, i::Integer, o::Ordering, len::Integer=length(xs)) = percolate_down!(xs, i, xs[i], o, len)
30+
Base.@propagate_inbounds percolate_down!(xs::AbstractArray, i::Integer, o::Ordering, len::Integer=length(xs)) = percolate_down!(xs, i, xs[i], o, len)
3031

3132

3233
# Binary min-heap percolate up.
33-
function percolate_up!(xs::AbstractArray, i::Integer, x=xs[i], o::Ordering=Forward)
34+
Base.@propagate_inbounds function percolate_up!(xs::AbstractArray, i::Integer, x, o::Ordering=Forward)
35+
@boundscheck checkbounds(xs, i)
36+
3437
@inbounds while (j = heapparent(i)) >= 1
3538
lt(o, x, xs[j]) || break
3639
xs[i] = xs[j]
3740
i = j
3841
end
39-
xs[i] = x
42+
@inbounds xs[i] = x
4043
end
41-
42-
@inline percolate_up!(xs::AbstractArray, i::Integer, o::Ordering) = percolate_up!(xs, i, xs[i], o)
44+
Base.@propagate_inbounds percolate_up!(xs::AbstractArray, i::Integer, o::Ordering) = percolate_up!(xs, i, xs[i], o)
4345

4446
"""
4547
heappop!(v, [ord])
@@ -48,10 +50,11 @@ Given a binary heap-ordered array, remove and return the lowest ordered element.
4850
For efficiency, this function does not check that the array is indeed heap-ordered.
4951
"""
5052
function heappop!(xs::AbstractArray, o::Ordering=Forward)
53+
Base.require_one_based_indexing(xs)
5154
x = xs[1]
5255
y = pop!(xs)
5356
if !isempty(xs)
54-
percolate_down!(xs, 1, y, o)
57+
@inbounds percolate_down!(xs, 1, y, o)
5558
end
5659
return x
5760
end
@@ -63,8 +66,9 @@ Given a binary heap-ordered array, push a new element `x`, preserving the heap p
6366
For efficiency, this function does not check that the array is indeed heap-ordered.
6467
"""
6568
@inline function heappush!(xs::AbstractArray, x, o::Ordering=Forward)
69+
Base.require_one_based_indexing(xs)
6670
push!(xs, x)
67-
percolate_up!(xs, length(xs), o)
71+
@inbounds percolate_up!(xs, length(xs), o)
6872
return xs
6973
end
7074

@@ -76,8 +80,9 @@ end
7680
In-place [`heapify`](@ref).
7781
"""
7882
@inline function heapify!(xs::AbstractArray, o::Ordering=Forward)
83+
Base.require_one_based_indexing(xs)
7984
for i in heapparent(length(xs)):-1:1
80-
percolate_down!(xs, i, o)
85+
@inbounds percolate_down!(xs, i, o)
8186
end
8287
return xs
8388
end
@@ -129,6 +134,7 @@ false
129134
```
130135
"""
131136
function isheap(xs::AbstractArray, o::Ordering=Forward)
137+
Base.require_one_based_indexing(xs)
132138
for i in 1:div(length(xs), 2)
133139
if lt(o, xs[heapleft(i)], xs[i]) ||
134140
(heapright(i) <= length(xs) && lt(o, xs[heapright(i)], xs[i]))

src/priorityqueue.jl

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct PriorityQueue{K,V,O<:Ordering} <: AbstractDict{K,V}
4949
function PriorityQueue{K,V,O}(o::O, itr) where {K,V,O<:Ordering}
5050
xs = Vector{Pair{K,V}}(undef, length(itr))
5151
index = Dict{K, Int}()
52-
for (i, (k, v)) in enumerate(itr)
52+
@inbounds for (i, (k, v)) in enumerate(itr)
5353
xs[i] = Pair{K,V}(k, v)
5454
if haskey(index, k)
5555
throw(ArgumentError("PriorityQueue keys must be unique"))
@@ -60,7 +60,7 @@ struct PriorityQueue{K,V,O<:Ordering} <: AbstractDict{K,V}
6060

6161
# heapify
6262
for i in heapparent(length(pq.xs)):-1:1
63-
percolate_down!(pq, i)
63+
@inbounds percolate_down!(pq, i)
6464
end
6565

6666
return pq
@@ -167,8 +167,10 @@ priority queue.
167167
"""
168168
Base.first(pq::PriorityQueue) = first(pq.xs)
169169

170-
function percolate_down!(pq::PriorityQueue, i::Integer)
171-
x = pq.xs[i]
170+
Base.@propagate_inbounds function percolate_down!(pq::PriorityQueue, i::Integer)
171+
@boundscheck checkbounds(pq.xs, i)
172+
173+
@inbounds x = pq.xs[i]
172174
@inbounds while (l = heapleft(i)) <= length(pq)
173175
r = heapright(i)
174176
j = r > length(pq) || lt(pq.o, pq.xs[l].second, pq.xs[r].second) ? l : r
@@ -182,12 +184,14 @@ function percolate_down!(pq::PriorityQueue, i::Integer)
182184
end
183185
end
184186
pq.index[x.first] = i
185-
pq.xs[i] = x
187+
@inbounds pq.xs[i] = x
186188
end
187189

188190

189-
function percolate_up!(pq::PriorityQueue, i::Integer)
190-
x = pq.xs[i]
191+
Base.@propagate_inbounds function percolate_up!(pq::PriorityQueue, i::Integer)
192+
@boundscheck checkbounds(pq.xs, i)
193+
194+
@inbounds x = pq.xs[i]
191195
@inbounds while i > 1
192196
j = heapparent(i)
193197
xj = pq.xs[j]
@@ -200,7 +204,7 @@ function percolate_up!(pq::PriorityQueue, i::Integer)
200204
end
201205
end
202206
pq.index[x.first] = i
203-
pq.xs[i] = x
207+
@inbounds pq.xs[i] = x
204208
end
205209

206210
# Equivalent to percolate_up! with an element having lower priority than any other
@@ -236,8 +240,8 @@ end
236240
# Change the priority of an existing element, or enqueue it if it isn't present.
237241
function Base.setindex!(pq::PriorityQueue{K, V}, value, key) where {K,V}
238242
i = get(pq.index, key, 0)
239-
if i != 0
240-
@inbounds oldvalue = pq.xs[i].second
243+
@inbounds if i != 0
244+
oldvalue = pq.xs[i].second
241245
pq.xs[i] = Pair{K,V}(key, value)
242246
if lt(pq.o, oldvalue, value)
243247
percolate_down!(pq, i)
@@ -255,7 +259,7 @@ end
255259
256260
Insert the a key `k` into a priority queue `pq` with priority `v`.
257261
258-
# Examples
262+
# Examples
259263
260264
```jldoctest
261265
julia> a = PriorityQueue("a" => 1, "b" => 2, "c" => 3, "e" => 5)
@@ -317,7 +321,7 @@ function Base.popfirst!(pq::PriorityQueue)
317321
if !isempty(pq)
318322
@inbounds pq.xs[1] = y
319323
pq.index[y.first] = 1
320-
percolate_down!(pq, 1)
324+
@inbounds percolate_down!(pq, 1)
321325
end
322326
delete!(pq.index, x.first)
323327
return x
@@ -354,7 +358,7 @@ function Base.delete!(pq::PriorityQueue, key)
354358
end
355359

356360
"""
357-
empty!(pq::PriorityQueue)
361+
empty!(pq::PriorityQueue)
358362
359363
Reset priority queue `pq`.
360364
"""

0 commit comments

Comments
 (0)