From dfec466a3a0be3aa46e802d3c9e3c5b13231afd1 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Tue, 26 Nov 2019 18:27:33 -0800 Subject: [PATCH 1/5] Remove Julia 0.7 from Travis and AppVeyor --- .travis.yml | 1 - appveyor.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index da200774a..f2b1db8a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ os: - linux - osx julia: - - 0.7 - 1.0 - nightly notifications: diff --git a/appveyor.yml b/appveyor.yml index 831beb78f..ef1ff1a05 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ environment: matrix: - - julia_version: 0.7 + - julia_version: 1.0 - julia_version: latest platform: From ccd0d4895026de2688336b8757cd92d8c37f610f Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Fri, 13 Sep 2019 23:03:47 -0700 Subject: [PATCH 2/5] Use Base.Ordering for heap, and other performance improvements --- benchmark/bench_heap.jl | 44 +++----- docs/src/heaps.md | 42 ++++++-- src/DataStructures.jl | 2 +- src/heaps.jl | 83 +++++++++------ src/heaps/arrays_as_heaps.jl | 7 +- src/heaps/binary_heap.jl | 176 +++++++++++-------------------- src/heaps/mutable_binary_heap.jl | 70 ++++++------ test/test_binheap.jl | 39 ++++++- test/test_minmax_heap.jl | 10 ++ test/test_mutable_binheap.jl | 30 ++++-- 10 files changed, 275 insertions(+), 228 deletions(-) diff --git a/benchmark/bench_heap.jl b/benchmark/bench_heap.jl index db70ec09f..5ff8e98c4 100644 --- a/benchmark/bench_heap.jl +++ b/benchmark/bench_heap.jl @@ -26,13 +26,12 @@ heaptypes = [BinaryHeap, MutableBinaryHeap] aexps = [1,3] datatypes = [Int, Float64] baseorderings = Dict( - "Min" => DataStructures.LessThan, - #"Max" => DataStructures.GreaterThan, + "Min" => Base.ForwardOrdering, + #"Max" => Base.ReverseOrdering, ) fastfloatorderings = Dict( - # These will be enabled upon reordering change - #"FastMin" => DataStructures.FasterForward(), - #"FastMax" => DataStructures.FasterReverse(), + "Min" => DataStructures.FasterForward, + "Max" => DataStructures.FasterReverse, ) for heap in heaptypes @@ -41,7 +40,8 @@ for heap in heaptypes Random.seed!(0) a = rand(dt, 10^aexp) - orderings = baseorderings + # Dict types to force use of abstract type if containing single value + orderings = Dict{String, DataType}(baseorderings) if dt == Float64 # swap to faster ordering operation for (k,v) in orderings @@ -66,38 +66,22 @@ for heap in heaptypes end end -# Quick check to ensure no Float regressions with Min/Max convenience functions -# These don't fit in well with the above loop, since ordering is hardcoded. -heapalias = Dict( - "BinaryMinHeap" => BinaryMinHeap, - "BinaryMaxHeap" => BinaryMaxHeap, - "BinaryMinMaxHeap" => BinaryMinMaxHeap, # <- no alias issue -) -for (heapname, heap) in heapalias - for aexp in aexps - for dt in [Float64] - Random.seed!(0) - a = rand(dt, 10^aexp) - prepath = [heapname] - postpath = [string(dt), "10^"*string(aexp)] - suite[vcat(prepath, ["make"], postpath)] = - @benchmarkable $(heap)($a) - suite[vcat(prepath, ["push"], postpath)] = - @benchmarkable push_heap(h, $a) setup=(h=$(heap){$dt}()) - suite[vcat(prepath, ["pop"], postpath)] = - @benchmarkable pop_heap(h) setup=(h=$(heap)($a)) - end - end -end +fast_extreme_orderings = Dict( + nsmallest => DataStructures.FasterForward(), + nlargest => DataStructures.FasterReverse(), + ) for func in [nlargest, nsmallest] + fastord = fast_extreme_orderings[func] for aexp in [4] Random.seed!(0); a = rand(10^aexp); for nexp in [2] n = 10^nexp - suite[[string(func), "a=rand(10^"*string(aexp)*")", "n=10^"*string(nexp)]] = + suite[["Slow " * string(func), "a=rand(10^"*string(aexp)*")", "n=10^"*string(nexp)]] = @benchmarkable $(func)($n, $a) + suite[[string(func), "a=rand(10^"*string(aexp)*")", "n=10^"*string(nexp)]] = + @benchmarkable DataStructures.nextreme($fastord, $n, $a) end end end diff --git a/docs/src/heaps.md b/docs/src/heaps.md index 0f7b7c4d3..162c165ac 100644 --- a/docs/src/heaps.md +++ b/docs/src/heaps.md @@ -7,18 +7,23 @@ All heaps in this package are derived from `AbstractHeap`, and provide the following interface: ```julia -# Let h be a heap, i be a handle, and v be a value. +# Let `h` be a heap, `v` be a value, and `n` be an integer size -length(h) # returns the number of elements +length(h) # returns the number of elements -isempty(h) # returns whether the heap is empty +isempty(h) # returns whether the heap is empty -push!(h, v) # add a value to the heap +push!(h, v) # add a value to the heap -first(h) # return the first (top) value of a heap +first(h) # return the first (top) value of a heap -pop!(h) # removes the first (top) value, and returns it +pop!(h) # removes the first (top) value, and returns it +extract_all!(h) # removes all elements and returns sorted array + +extract_all_rev!(h) # removes all elements and returns reverse sorted array + +sizehint!(h, n) # reserve capacity for at least `n` elements ``` Mutable heaps (values can be changed after being pushed to a heap) are @@ -26,6 +31,8 @@ derived from `AbstractMutableHeap <: AbstractHeap`, and additionally provides the following interface: ```julia +# Let `h` be a heap, `i` be a handle, and `v` be a value. + i = push!(h, v) # adds a value to the heap and and returns a handle to v update!(h, i, v) # updates the value of an element (referred to by the handle i) @@ -54,6 +61,21 @@ h = MutableBinaryMinHeap([1,4,3,2]) h = MutableBinaryMaxHeap([1,4,3,2]) # create a mutable min/max heap from a vector ``` +Heaps may be constructed with a custom ordering. One use case for custom orderings +is to achieve faster performance with `Float` elements with the risk of random ordering +if any elements are `NaN`. The provided `DataStructures.FasterForward` and +`DataStructures.FasterReverse` orderings are optimized for this purpose. +Custom orderings may also be used for defining the order of structs as heap elements. +```julia +h = BinaryHeap{Float64, DataStructures.FasterForward}() # faster min heap +h = BinaryHeap{Float64, DataStructures.FasterReverse}() # faster max heap + +h = MutableBinaryHeap{Float64, DataStructures.FasterForward}() # faster mutable min heap +h = MutableBinaryHeap{Float64, DataStructures.FasterReverse}() # faster mutable max heap + +h = BinaryHeap{MyStruct, MyStructOrdering}() # heap containing custom struct +``` + ## Min-max heaps Min-max heaps maintain the minimum _and_ the maximum of a set, allowing both to be retrieved in constant (`O(1)`) time. @@ -97,5 +119,9 @@ nlargest(3, [0,21,-12,68,-25,14]) # => [68,21,14] nsmallest(3, [0,21,-12,68,-25,14]) # => [-25,-12,0] ``` -`nlargest(n, a)` is equivalent to `sort(a, lt = >)[1:min(n, end)]`, and -`nsmallest(n, a)` is equivalent to `sort(a, lt = <)[1:min(n, end)]`. +Note that if the array contains floats and is free of NaN values, +then the following alternatives may be used to achieve a 2x performance boost. +``` +DataStructures.nextreme(DataStructures.FasterReverse(), n, a) # faster nlargest(n, a) +DataStructures.nextreme(DataStructures.FasterForward(), n, a) # faster nsmallest(n, a) +``` diff --git a/src/DataStructures.jl b/src/DataStructures.jl index 6bc30df93..09ab05e3f 100644 --- a/src/DataStructures.jl +++ b/src/DataStructures.jl @@ -30,7 +30,7 @@ module DataStructures export IntDisjointSets, DisjointSets, num_groups, find_root!, in_same_set, root_union! export FenwickTree, length, inc!, dec!, incdec!, prefixsum - export AbstractHeap, compare, extract_all! + export AbstractHeap, compare, extract_all!, extract_all_rev! export BinaryHeap, BinaryMinHeap, BinaryMaxHeap, nlargest, nsmallest export MutableBinaryHeap, MutableBinaryMinHeap, MutableBinaryMaxHeap export heapify!, heapify, heappop!, heappush!, isheap diff --git a/src/heaps.jl b/src/heaps.jl index f4d370521..25e12243e 100644 --- a/src/heaps.jl +++ b/src/heaps.jl @@ -24,7 +24,7 @@ # # - sizehint!(h, s) set size hint to a heap # -# - first(h) return the first (top) value of a heap +# - first(h) return the first (top) value of a heap # # - pop!(h) removes the first (top) value, and # returns it @@ -37,7 +37,7 @@ # - update!(h, i, v) updates the value of an element # (referred to by the handle i) # -# - delete!(h, i) deletes the node with +# - delete!(h, i) deletes the node with # handle i from the heap # # - top_with_handle(h) return the top value of a heap @@ -55,28 +55,24 @@ abstract type AbstractMutableHeap{VT,HT} <: AbstractHeap{VT} end abstract type AbstractMinMaxHeap{VT} <: AbstractHeap{VT} end -# comparer - -struct LessThan -end - -struct GreaterThan -end - -compare(c::LessThan, x, y) = x < y -compare(c::GreaterThan, x, y) = x > y - # heap implementations include("heaps/binary_heap.jl") include("heaps/mutable_binary_heap.jl") -include("heaps/arrays_as_heaps.jl") include("heaps/minmax_heap.jl") # generic functions Base.eltype(::Type{<:AbstractHeap{T}}) where T = T +""" + extract_all!(h) + +Return an array of heap elements in sorted order (heap head at first index). + +Note that for simple heaps (not mutable or minmax) +sorting the internal array of elements in-place is faster. +""" function extract_all!(h::AbstractHeap{VT}) where VT n = length(h) r = Vector{VT}(undef, n) @@ -86,6 +82,14 @@ function extract_all!(h::AbstractHeap{VT}) where VT return r end +""" + extract_all_rev!(h) + +Return an array of heap elements in reverse sorted order (heap head at last index). + +Note that for simple heaps (not mutable or minmax) +sorting the internal array of elements in-place is faster. +""" function extract_all_rev!(h::AbstractHeap{VT}) where VT n = length(h) r = Vector{VT}(undef, n) @@ -97,30 +101,31 @@ end # Array functions using heaps -function nextreme(comp::Comp, n::Int, arr::AbstractVector{T}) where {T, Comp} +""" + nextreme(ord, n, arr) + +return an array of the first `n` values of `arr` sorted by `ord`. +""" +function nextreme(ord::Base.Ordering, n::Int, arr::AbstractVector{T}) where T if n <= 0 return T[] # sort(arr)[1:n] returns [] for n <= 0 elseif n >= length(arr) - return sort(arr, lt = (x, y) -> compare(comp, y, x)) + return sort(arr, order = ord) end - buffer = BinaryHeap{T,Comp}() + rev = Base.ReverseOrdering(ord) - for i = 1 : n - @inbounds xi = arr[i] - push!(buffer, xi) - end + buffer = heapify(arr[1:n], rev) for i = n + 1 : length(arr) @inbounds xi = arr[i] - if compare(comp, first(buffer), xi) - # This could use a pushpop method - pop!(buffer) - push!(buffer, xi) + if Base.lt(rev, buffer[1], xi) + buffer[1] = xi + percolate_down!(buffer, 1, rev) end end - return extract_all_rev!(buffer) + return sort!(buffer, order = ord) end """ @@ -128,10 +133,17 @@ end Return the `n` largest elements of the array `arr`. -Equivalent to `sort(arr, lt = >)[1:min(n, end)]` +Equivalent to: + sort(arr, order = Base.Reverse)[1:min(n, end)] + +Note that if `arr` contains floats and is free of NaN values, +then the following alternative may be used to achieve 2x performance. + DataStructures.nextreme(DataStructures.FasterReverse(), n, arr) +This faster version is equivalent to: + sort(arr, lt = >)[1:min(n, end)] """ -function nlargest(n::Int, arr::AbstractVector{T}) where T - return nextreme(LessThan(), n, arr) +function nlargest(n::Int, arr::AbstractVector) + return nextreme(Base.Reverse, n, arr) end """ @@ -139,8 +151,15 @@ end Return the `n` smallest elements of the array `arr`. -Equivalent to `sort(arr, lt = <)[1:min(n, end)]` +Equivalent to: + sort(arr, order = Base.Forward)[1:min(n, end)] + +Note that if `arr` contains floats and is free of NaN values, +then the following alternative may be used to achieve 2x performance. + DataStructures.nextreme(DataStructures.FasterForward(), n, arr) +This faster version is equivalent to: + sort(arr, lt = <)[1:min(n, end)] """ -function nsmallest(n::Int, arr::AbstractVector{T}) where T - return nextreme(GreaterThan(), n, arr) +function nsmallest(n::Int, arr::AbstractVector) + return nextreme(Base.Forward, n, arr) end diff --git a/src/heaps/arrays_as_heaps.jl b/src/heaps/arrays_as_heaps.jl index 9bd49887a..9d184fdd0 100644 --- a/src/heaps/arrays_as_heaps.jl +++ b/src/heaps/arrays_as_heaps.jl @@ -44,7 +44,7 @@ function percolate_up!(xs::AbstractArray, i::Integer, x=xs[i], o::Ordering=Forwa xs[i] = x end -percolate_up!(xs::AbstractArray{T}, i::Integer, o::Ordering) where {T} = percolate_up!(xs, i, xs[i], o) +percolate_up!(xs::AbstractArray, i::Integer, o::Ordering) = percolate_up!(xs, i, xs[i], o) """ heappop!(v, [ord]) @@ -69,12 +69,12 @@ For efficiency, this function does not check that the array is indeed heap-order """ function heappush!(xs::AbstractArray, x, o::Ordering=Forward) push!(xs, x) - percolate_up!(xs, length(xs), x, o) + percolate_up!(xs, length(xs), o) return xs end -# Turn an arbitrary array into a binary min-heap in linear time. +# Turn an arbitrary array into a binary min-heap (by default) in linear time. """ heapify!(v, ord::Ordering=Forward) @@ -111,6 +111,7 @@ julia> heapify(a, Base.Order.Reverse) 2 ``` """ +# Todo, benchmarking shows copy(xs) outperforms copyto!(similar(xs), xs) for 10^6 Float64 heapify(xs::AbstractArray, o::Ordering=Forward) = heapify!(copyto!(similar(xs), xs), o) """ diff --git a/src/heaps/binary_heap.jl b/src/heaps/binary_heap.jl index 15084c160..5408c5f79 100644 --- a/src/heaps/binary_heap.jl +++ b/src/heaps/binary_heap.jl @@ -1,145 +1,82 @@ # Binary heap (non-mutable) +include("arrays_as_heaps.jl") + ################################################# # -# core implementation +# heap type and constructors # ################################################# -function _heap_bubble_up!(comp::Comp, valtree::Array{T}, i::Int) where {Comp,T} - i0::Int = i - @inbounds v = valtree[i] - - while i > 1 # nd is not root - p = i >> 1 - @inbounds vp = valtree[p] - - if compare(comp, v, vp) - # move parent downward - @inbounds valtree[i] = vp - i = p - else - break - end - end - - if i != i0 - @inbounds valtree[i] = v - end -end - -function _heap_bubble_down!(comp::Comp, valtree::Array{T}, i::Int) where {Comp,T} - @inbounds v::T = valtree[i] - swapped = true - n = length(valtree) - last_parent = n >> 1 - - while swapped && i <= last_parent - lc = i << 1 - if lc < n # contains both left and right children - rc = lc + 1 - @inbounds lv = valtree[lc] - @inbounds rv = valtree[rc] - if compare(comp, rv, lv) - if compare(comp, rv, v) - @inbounds valtree[i] = rv - i = rc - else - swapped = false - end - else - if compare(comp, lv, v) - @inbounds valtree[i] = lv - i = lc - else - swapped = false - end - end - else # contains only left child - @inbounds lv = valtree[lc] - if compare(comp, lv, v) - @inbounds valtree[i] = lv - i = lc - else - swapped = false - end - end - end - - valtree[i] = v -end - - -function _binary_heap_pop!(comp::Comp, valtree::Array{T}) where {Comp,T} - # extract root - v = valtree[1] - - if length(valtree) == 1 - empty!(valtree) - else - valtree[1] = pop!(valtree) - if length(valtree) > 1 - _heap_bubble_down!(comp, valtree, 1) - end - end - return v -end - +""" + FasterForward() -function _make_binary_heap(comp::Comp, ty::Type{T}, xs) where {Comp,T} - n = length(xs) - valtree = copy(xs) - for i = 2 : n - _heap_bubble_up!(comp, valtree, i) - end - return valtree -end +FasterForward enables 2x faster float comparison versus Base.ForwardOrdering, +but ordering is undefined if the data contains NaN values. +Enable this higher-performance option by calling the BinaryHeap +constructor instead of the BinaryMinHeap helper constructor. +""" +struct FasterForward <: Base.Ordering end +Base.lt(o::FasterForward, a, b) = a < b +""" + FasterReverse() -################################################# -# -# heap type and constructors -# -################################################# +FasterReverse enables 2x faster float comparison versus Base.ReverseOrdering, +but ordering is undefined if the data contains NaN values. +Enable this higher-performance option by calling the BinaryHeap +constructor instead of the BinaryMaxHeap helper constructor. +""" +struct FasterReverse <: Base.Ordering end +Base.lt(o::FasterReverse, a, b) = a > b -mutable struct BinaryHeap{T,Comp} <: AbstractHeap{T} - comparer::Comp +mutable struct BinaryHeap{T, O <: Base.Ordering} <: AbstractHeap{T} + ordering::O valtree::Vector{T} - BinaryHeap{T,Comp}() where {T,Comp} = new{T,Comp}(Comp(), Vector{T}()) + function BinaryHeap{T, O}() where {T,O} + new{T,O}(O(), Vector{T}()) + end - function BinaryHeap{T,Comp}(xs::AbstractVector{T}) where {T,Comp} - valtree = _make_binary_heap(Comp(), T, xs) - new{T,Comp}(Comp(), valtree) + function BinaryHeap{T, O}(xs::AbstractVector{T}) where {T,O} + ordering = O() + valtree = heapify(xs, ordering) + new{T,O}(ordering, valtree) end end -const BinaryMinHeap{T} = BinaryHeap{T, LessThan} -const BinaryMaxHeap{T} = BinaryHeap{T, GreaterThan} - +const BinaryMinHeap{T} = BinaryHeap{T, Base.ForwardOrdering} +const BinaryMaxHeap{T} = BinaryHeap{T, Base.ReverseOrdering} BinaryMinHeap(xs::AbstractVector{T}) where T = BinaryMinHeap{T}(xs) BinaryMaxHeap(xs::AbstractVector{T}) where T = BinaryMaxHeap{T}(xs) - ################################################# # # interfaces # ################################################# +""" + length(h::BinaryHeap) + +Returns the number of elements in heap `h`. +""" length(h::BinaryHeap) = length(h.valtree) +""" + isempty(h::BinaryHeap) + +Returns whether the heap `h` is empty. +""" isempty(h::BinaryHeap) = isempty(h.valtree) -function push!(h::BinaryHeap, v) - valtree = h.valtree - push!(valtree, v) - _heap_bubble_up!(h.comparer, valtree, length(valtree)) - return h -end +""" + push!(h::BinaryHeap, value) -function sizehint!(h::BinaryHeap, s::Integer) - sizehint!(h.valtree, s) +Adds the `value` element to the heap `h`. +""" +function push!(h::BinaryHeap, v) + heappush!(h.valtree, v, h.ordering) return h end @@ -150,4 +87,19 @@ Returns the element at the top of the heap `h`. """ @inline first(h::BinaryHeap) = h.valtree[1] -pop!(h::BinaryHeap{T}) where {T} = _binary_heap_pop!(h.comparer, h.valtree) +""" + pop!(h::BinaryHeap) + +Removes and returns the element at the top of the heap `h`. +""" +pop!(h::BinaryHeap) = heappop!(h.valtree, h.ordering) + +""" + sizehint!(h::BinaryHeap, n::Integer) + +Suggest that heap `h` reserve capacity for at least `n` elements. This can improve performance. +""" +function sizehint!(h::BinaryHeap, n::Integer) + sizehint!(h.valtree, n) + return h +end diff --git a/src/heaps/mutable_binary_heap.jl b/src/heaps/mutable_binary_heap.jl index 5df4f74eb..bf63ee452 100644 --- a/src/heaps/mutable_binary_heap.jl +++ b/src/heaps/mutable_binary_heap.jl @@ -11,8 +11,8 @@ end # ################################################# -function _heap_bubble_up!(comp::Comp, - nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int) where {Comp, T} +function _heap_bubble_up!(ord::Ordering, + nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int) where T @inbounds nd = nodes[nd_id] v::T = nd.value @@ -24,7 +24,7 @@ function _heap_bubble_up!(comp::Comp, p = i >> 1 @inbounds nd_p = nodes[p] - if compare(comp, v, nd_p.value) + if Base.lt(ord, v, nd_p.value) # move parent downward @inbounds nodes[i] = nd_p @inbounds nodemap[nd_p.handle] = i @@ -40,8 +40,8 @@ function _heap_bubble_up!(comp::Comp, end end -function _heap_bubble_down!(comp::Comp, - nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int) where {Comp, T} +function _heap_bubble_down!(ord::Ordering, + nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int) where T @inbounds nd = nodes[nd_id] v::T = nd.value @@ -62,9 +62,9 @@ function _heap_bubble_down!(comp::Comp, @inbounds nd_l = nodes[il] @inbounds nd_r = nodes[ir] - if compare(comp, nd_r.value, nd_l.value) + if Base.lt(ord, nd_r.value, nd_l.value) # consider right child - if compare(comp, nd_r.value, v) + if Base.lt(ord, nd_r.value, v) @inbounds nodes[i] = nd_r @inbounds nodemap[nd_r.handle] = i i = ir @@ -73,7 +73,7 @@ function _heap_bubble_down!(comp::Comp, end else # consider left child - if compare(comp, nd_l.value, v) + if Base.lt(ord, nd_l.value, v) @inbounds nodes[i] = nd_l @inbounds nodemap[nd_l.handle] = i i = il @@ -84,7 +84,7 @@ function _heap_bubble_down!(comp::Comp, else # contains only left child nd_l = nodes[il] - if compare(comp, nd_l.value, v) + if Base.lt(ord, nd_l.value, v) @inbounds nodes[i] = nd_l @inbounds nodemap[nd_l.handle] = i i = il @@ -100,8 +100,8 @@ function _heap_bubble_down!(comp::Comp, end end -function _binary_heap_pop!(comp::Comp, - nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int=1) where {Comp,T} +function _binary_heap_pop!(ord::Ordering, + nodes::Vector{MutableBinaryHeapNode{T}}, nodemap::Vector{Int}, nd_id::Int=1) where T # extract node rt = nodes[nd_id] @@ -118,17 +118,17 @@ function _binary_heap_pop!(comp::Comp, @inbounds nodemap[new_rt.handle] = nd_id if length(nodes) > 1 - if compare(comp, new_rt.value, v) - _heap_bubble_up!(comp, nodes, nodemap, nd_id) + if Base.lt(ord, new_rt.value, v) + _heap_bubble_up!(ord, nodes, nodemap, nd_id) else - _heap_bubble_down!(comp, nodes, nodemap, nd_id) + _heap_bubble_down!(ord, nodes, nodemap, nd_id) end end end return v end -function _make_mutable_binary_heap(comp::Comp, ty::Type{T}, values) where {Comp,T} +function _make_mutable_binary_heap(ord::Ordering, ty::Type{T}, values) where T # make a static binary index tree from a list of values n = length(values) @@ -143,7 +143,7 @@ function _make_mutable_binary_heap(comp::Comp, ty::Type{T}, values) where {Comp, end for i = 1 : n - _heap_bubble_up!(comp, nodes, nodemap, i) + _heap_bubble_up!(ord, nodes, nodemap, i) end return nodes, nodemap end @@ -155,25 +155,27 @@ end # ################################################# -mutable struct MutableBinaryHeap{VT, Comp} <: AbstractMutableHeap{VT,Int} - comparer::Comp - nodes::Vector{MutableBinaryHeapNode{VT}} +mutable struct MutableBinaryHeap{T, O <: Base.Ordering} <: AbstractMutableHeap{T, Int} + ordering::O + nodes::Vector{MutableBinaryHeapNode{T}} node_map::Vector{Int} - function MutableBinaryHeap{VT, Comp}() where {VT, Comp} - nodes = Vector{MutableBinaryHeapNode{VT}}() + function MutableBinaryHeap{T, O}() where {T, O} + ordering = O() + nodes = Vector{MutableBinaryHeapNode{T}}() node_map = Vector{Int}() - new{VT, Comp}(Comp(), nodes, node_map) + new{T, O}(ordering, nodes, node_map) end - function MutableBinaryHeap{VT, Comp}(xs::AbstractVector{VT}) where {VT, Comp} - nodes, node_map = _make_mutable_binary_heap(Comp(), VT, xs) - new{VT, Comp}(Comp(), nodes, node_map) + function MutableBinaryHeap{T, O}(xs::AbstractVector{T}) where {T, O} + ordering = O() + nodes, node_map = _make_mutable_binary_heap(ordering, T, xs) + new{T, O}(ordering, nodes, node_map) end end -const MutableBinaryMinHeap{T} = MutableBinaryHeap{T, LessThan} -const MutableBinaryMaxHeap{T} = MutableBinaryHeap{T, GreaterThan} +const MutableBinaryMinHeap{T} = MutableBinaryHeap{T, Base.ForwardOrdering} +const MutableBinaryMaxHeap{T} = MutableBinaryHeap{T, Base.ReverseOrdering} MutableBinaryMinHeap(xs::AbstractVector{T}) where T = MutableBinaryMinHeap{T}(xs) MutableBinaryMaxHeap(xs::AbstractVector{T}) where T = MutableBinaryMaxHeap{T}(xs) @@ -209,7 +211,7 @@ function push!(h::MutableBinaryHeap{T}, v) where T nd_id = length(nodes) + 1 push!(nodes, MutableBinaryHeapNode(convert(T, v), i)) push!(nodemap, nd_id) - _heap_bubble_up!(h.comparer, nodes, nodemap, nd_id) + _heap_bubble_up!(h.ordering, nodes, nodemap, nd_id) return i end @@ -231,7 +233,7 @@ function top_with_handle(h::MutableBinaryHeap) return el.value, el.handle end -pop!(h::MutableBinaryHeap{T}) where {T} = _binary_heap_pop!(h.comparer, h.nodes, h.node_map) +pop!(h::MutableBinaryHeap{T}) where {T} = _binary_heap_pop!(h.ordering, h.nodes, h.node_map) """ update!{T}(h::MutableBinaryHeap{T}, i::Int, v::T) @@ -242,16 +244,16 @@ This is equivalent to `h[i]=v`. function update!(h::MutableBinaryHeap{T}, i::Int, v) where T nodes = h.nodes nodemap = h.node_map - comp = h.comparer + ordering = h.ordering nd_id = nodemap[i] v0 = nodes[nd_id].value x = convert(T, v) nodes[nd_id] = MutableBinaryHeapNode(x, i) - if compare(comp, x, v0) - _heap_bubble_up!(comp, nodes, nodemap, nd_id) + if Base.lt(ordering, x, v0) + _heap_bubble_up!(ordering, nodes, nodemap, nd_id) else - _heap_bubble_down!(comp, nodes, nodemap, nd_id) + _heap_bubble_down!(ordering, nodes, nodemap, nd_id) end end @@ -262,7 +264,7 @@ Deletes the element with handle `i` from heap `h` . """ function delete!(h::MutableBinaryHeap{T}, i::Int) where T nd_id = h.node_map[i] - _binary_heap_pop!(h.comparer, h.nodes, h.node_map, nd_id) + _binary_heap_pop!(h.ordering, h.nodes, h.node_map, nd_id) return h end diff --git a/test/test_binheap.jl b/test/test_binheap.jl index 685291735..6a24aa8f3 100644 --- a/test/test_binheap.jl +++ b/test/test_binheap.jl @@ -5,13 +5,41 @@ @testset "make heap" begin vs = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7] + @testset "construct heap" begin + BinaryHeap{Int, Base.ForwardOrdering}() + BinaryHeap{Int, Base.ForwardOrdering}(vs) + + BinaryHeap{Int, Base.ReverseOrdering}() + BinaryHeap{Int, Base.ReverseOrdering}(vs) + + BinaryMinHeap{Int}() + BinaryMinHeap{Int}(vs) + BinaryMinHeap(vs) + + BinaryMaxHeap{Int}() + BinaryMaxHeap{Int}(vs) + BinaryMaxHeap(vs) + + @test true + end + + @testset "confirm heap" begin + @test isheap([1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) + @test isheap([16, 14, 10, 8, 7, 3, 9, 1, 4, 2], Base.Reverse) + + @test !isheap([16, 14, 10, 8, 7, 3, 9, 1, 4, 2]) + @test !isheap([1, 2, 3, 4, 7, 9, 10, 14, 8, 16], Base.Reverse) + @test !isheap([15, 2, 3, 4, 7, 9, 10, 14, 8, 16]) + @test !isheap([15, 2, 3, 4, 7, 9, 10, 14, 8, 16], Base.Reverse) + end + @testset "make min heap" begin h = BinaryMinHeap(vs) @test length(h) == 10 @test !isempty(h) @test first(h) == 1 - @test isequal(h.valtree, [1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) + @test isheap([1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) @test sizehint!(h, 100) === h end @@ -21,10 +49,15 @@ @test length(h) == 10 @test !isempty(h) @test first(h) == 16 - @test isequal(h.valtree, [16, 14, 10, 8, 7, 3, 9, 1, 4, 2]) + @test isheap([16, 14, 10, 8, 7, 3, 9, 1, 4, 2], Base.Reverse) @test sizehint!(h, 100) === h end + @testset "extract all" begin + @test sort(vs) == extract_all!(BinaryMinHeap(vs)) + @test reverse(sort(vs)) == extract_all_rev!(BinaryMinHeap(vs)) + end + @testset "push!" begin @testset "push! hmin" begin hmin = BinaryMinHeap{Int}() @@ -124,6 +157,8 @@ for n = -1:length(ss) + 1 @test sort(ss, lt = >)[1:min(n, end)] == nlargest(n, ss) @test sort(ss, lt = <)[1:min(n, end)] == nsmallest(n, ss) + @test nlargest(n, ss) == DataStructures.nextreme(DataStructures.FasterReverse(), n, ss) + @test nsmallest(n, ss) == DataStructures.nextreme(DataStructures.FasterForward(), n, ss) end end diff --git a/test/test_minmax_heap.jl b/test/test_minmax_heap.jl index 5a92be911..aa63cbf89 100644 --- a/test/test_minmax_heap.jl +++ b/test/test_minmax_heap.jl @@ -4,6 +4,16 @@ using Base.Order: Forward, Reverse @testset "Binary MinMax Heaps" begin + @testset "construct heap" begin + vs = [10, 4, 6, 1, 16, 2, 20, 17, 13, 5] + + BinaryMinMaxHeap{Int}() + + BinaryMinMaxHeap(vs) + + @test true + end + @testset "is_minmax_heap tests" begin mmheap = [0, 10, 9, 2, 3, 4, 5] @test is_minmax_heap(mmheap) diff --git a/test/test_mutable_binheap.jl b/test/test_mutable_binheap.jl index 7c0a0b542..064192f19 100644 --- a/test/test_mutable_binheap.jl +++ b/test/test_mutable_binheap.jl @@ -2,7 +2,7 @@ # auxiliary functions -function heap_values(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} +function heap_values(h::MutableBinaryHeap{VT,O}) where {VT,O} n = length(h) nodes = h.nodes @assert length(nodes) == n @@ -13,7 +13,7 @@ function heap_values(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} vs end -function list_values(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} +function list_values(h::MutableBinaryHeap{VT,O}) where {VT,O} n = length(h) nodes = h.nodes nodemap = h.node_map @@ -27,8 +27,8 @@ function list_values(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} vs end -function verify_heap(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} - comp = h.comparer +function verify_heap(h::MutableBinaryHeap{VT,O}) where {VT,O} + ord = h.ordering nodes = h.nodes n = length(h) m = div(n,2) @@ -36,13 +36,13 @@ function verify_heap(h::MutableBinaryHeap{VT,Comp}) where {VT,Comp} v = nodes[i].value lc = i * 2 if lc <= n - if compare(comp, nodes[lc].value, v) + if Base.lt(ord, nodes[lc].value, v) return false end end rc = lc + 1 if rc <= n - if compare(comp, nodes[rc].value, v) + if Base.lt(ord, nodes[rc].value, v) return false end end @@ -54,6 +54,24 @@ end vs = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7] + @testset "construct heap" begin + MutableBinaryHeap{Int, Base.ForwardOrdering}() + MutableBinaryHeap{Int, Base.ForwardOrdering}(vs) + + MutableBinaryHeap{Int, Base.ReverseOrdering}() + MutableBinaryHeap{Int, Base.ReverseOrdering}(vs) + + MutableBinaryMinHeap{Int}() + MutableBinaryMinHeap{Int}(vs) + MutableBinaryMinHeap(vs) + + MutableBinaryMaxHeap{Int}() + MutableBinaryMaxHeap{Int}(vs) + MutableBinaryMaxHeap(vs) + + @test true + end + @testset "basic tests" begin h = MutableBinaryMinHeap{Int}() From 3014ac5706d008fa235a1f7534467fcf6aef5f72 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Mon, 2 Dec 2019 16:30:58 -0800 Subject: [PATCH 3/5] Add BinaryMinMaxHeap constructor for combined type and data --- src/heaps/minmax_heap.jl | 4 +++- test/test_minmax_heap.jl | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/heaps/minmax_heap.jl b/src/heaps/minmax_heap.jl index f3d3cd5fc..d33d5d3fc 100644 --- a/src/heaps/minmax_heap.jl +++ b/src/heaps/minmax_heap.jl @@ -9,12 +9,14 @@ mutable struct BinaryMinMaxHeap{T} <: AbstractMinMaxHeap{T} BinaryMinMaxHeap{T}() where {T} = new{T}(Vector{T}()) - function BinaryMinMaxHeap(xs::AbstractVector{T}) where {T} + function BinaryMinMaxHeap{T}(xs::AbstractVector{T}) where {T} valtree = _make_binary_minmax_heap(xs) new{T}(valtree) end end +BinaryMinMaxHeap(xs::AbstractVector{T}) where T = BinaryMinMaxHeap{T}(xs) + ################################################ # # core implementation diff --git a/test/test_minmax_heap.jl b/test/test_minmax_heap.jl index aa63cbf89..d70a19bca 100644 --- a/test/test_minmax_heap.jl +++ b/test/test_minmax_heap.jl @@ -8,7 +8,7 @@ using Base.Order: Forward, Reverse vs = [10, 4, 6, 1, 16, 2, 20, 17, 13, 5] BinaryMinMaxHeap{Int}() - + BinaryMinMaxHeap{Int}(vs) BinaryMinMaxHeap(vs) @test true From 776587b05dca81c4ef53a9fa40178f50cd591e47 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 20 Nov 2019 22:53:34 -0800 Subject: [PATCH 4/5] ReverseOrdering() from Compat. Todo: Revert me later --- Project.toml | 2 ++ src/DataStructures.jl | 1 + 2 files changed, 3 insertions(+) diff --git a/Project.toml b/Project.toml index 39236ee95..fb4b14b10 100644 --- a/Project.toml +++ b/Project.toml @@ -3,10 +3,12 @@ uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" version = "0.18.0-DEV" [deps] +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" [compat] +Compat = "3.0.0" OrderedCollections = "1.1.0" julia = "1" diff --git a/src/DataStructures.jl b/src/DataStructures.jl index 09ab05e3f..4c34030d8 100644 --- a/src/DataStructures.jl +++ b/src/DataStructures.jl @@ -15,6 +15,7 @@ module DataStructures zero, checkbounds + using Compat # Provides Base.Order.ReverseOrdering(). May remove this line with julia 1.4 using OrderedCollections import OrderedCollections: filter, filter!, isordered export OrderedDict, OrderedSet, LittleDict From 2e465b8b76226a6b70b1727a859eda9d36760c21 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Thu, 4 Jun 2020 19:53:30 -0600 Subject: [PATCH 5/5] Allow implicit conversion --- src/heaps/binary_heap.jl | 2 +- test/test_binheap.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/heaps/binary_heap.jl b/src/heaps/binary_heap.jl index 5408c5f79..93dd5d0d6 100644 --- a/src/heaps/binary_heap.jl +++ b/src/heaps/binary_heap.jl @@ -38,7 +38,7 @@ mutable struct BinaryHeap{T, O <: Base.Ordering} <: AbstractHeap{T} new{T,O}(O(), Vector{T}()) end - function BinaryHeap{T, O}(xs::AbstractVector{T}) where {T,O} + function BinaryHeap{T, O}(xs) where {T,O} ordering = O() valtree = heapify(xs, ordering) new{T,O}(ordering, valtree) diff --git a/test/test_binheap.jl b/test/test_binheap.jl index 6a24aa8f3..a82f633f5 100644 --- a/test/test_binheap.jl +++ b/test/test_binheap.jl @@ -23,6 +23,14 @@ @test true end + @testset "implicit conversion" begin + BinaryHeap{Float64, Base.ForwardOrdering}(vs) + BinaryMinHeap{Float64}(vs) + BinaryMaxHeap{Float64}(vs) + + @test true + end + @testset "confirm heap" begin @test isheap([1, 2, 3, 4, 7, 9, 10, 14, 8, 16]) @test isheap([16, 14, 10, 8, 7, 3, 9, 1, 4, 2], Base.Reverse)