From a0b1431ac1889d7399138860fed26b2fdeb487a6 Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Tue, 1 Sep 2020 12:09:40 -0600 Subject: [PATCH 1/7] (Mutable)BinaryHeap constructors taking Base.Ordering instances --- src/heaps/binary_heap.jl | 18 +++++++++++++----- src/heaps/mutable_binary_heap.jl | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/heaps/binary_heap.jl b/src/heaps/binary_heap.jl index fe6802360..dd0ec3340 100644 --- a/src/heaps/binary_heap.jl +++ b/src/heaps/binary_heap.jl @@ -34,22 +34,30 @@ mutable struct BinaryHeap{T, O <: Base.Ordering} <: AbstractHeap{T} ordering::O valtree::Vector{T} - function BinaryHeap{T, O}() where {T,O} - new{T,O}(O(), Vector{T}()) + function BinaryHeap{T}(ordering::Base.Ordering) where T + new{T, typeof(ordering)}(ordering, Vector{T}()) end - function BinaryHeap{T, O}(xs) where {T,O} - ordering = O() + function BinaryHeap{T}(ordering::Base.Ordering, xs::AbstractVector) where T valtree = heapify(xs, ordering) - new{T,O}(ordering, valtree) + new{T, typeof(ordering)}(ordering, valtree) end end +BinaryHeap(ordering::Base.Ordering, xs::AbstractVector{T}) where T = BinaryHeap{T}(ordering, xs) + +# Constructors using singleton order types as type parameters rather than arguments +BinaryHeap{T, O}() where {T, O<:Base.Ordering} = BinaryHeap{T}(O()) +BinaryHeap{T, O}(xs::AbstractVector) where {T, O<:Base.Ordering} = BinaryHeap{T}(O(), xs) + +# Forward/reverse ordering type aliases 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 diff --git a/src/heaps/mutable_binary_heap.jl b/src/heaps/mutable_binary_heap.jl index 7d9f53edb..d6f26a966 100644 --- a/src/heaps/mutable_binary_heap.jl +++ b/src/heaps/mutable_binary_heap.jl @@ -160,26 +160,32 @@ mutable struct MutableBinaryHeap{T, O <: Base.Ordering} <: AbstractMutableHeap{T nodes::Vector{MutableBinaryHeapNode{T}} node_map::Vector{Int} - function MutableBinaryHeap{T, O}() where {T, O} - ordering = O() + function MutableBinaryHeap{T}(ordering::Base.Ordering) where T nodes = Vector{MutableBinaryHeapNode{T}}() node_map = Vector{Int}() - new{T, O}(ordering, nodes, node_map) + new{T, typeof(ordering)}(ordering, nodes, node_map) end - function MutableBinaryHeap{T, O}(xs::AbstractVector{T}) where {T, O} - ordering = O() + function MutableBinaryHeap{T}(ordering::Base.Ordering, xs::AbstractVector) where T nodes, node_map = _make_mutable_binary_heap(ordering, T, xs) - new{T, O}(ordering, nodes, node_map) + new{T, typeof(ordering)}(ordering, nodes, node_map) end end +MutableBinaryHeap(ordering::Base.Ordering, xs::AbstractVector{T}) where T = MutableBinaryHeap{T}(ordering, xs) + +# Constructors using singleton order types as type parameters rather than arguments +MutableBinaryHeap{T, O}() where {T, O<:Base.Ordering} = MutableBinaryHeap{T}(O()) +MutableBinaryHeap{T, O}(xs::AbstractVector) where {T, O<:Base.Ordering} = MutableBinaryHeap{T}(O(), xs) + +# Forward/reverse ordering type aliases 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) + function Base.show(io::IO, h::MutableBinaryHeap) print(io, "MutableBinaryHeap(") nodes = h.nodes From 503ba4d6637b2f5fd7752cd29a87b36a4a6dab33 Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Tue, 1 Sep 2020 12:19:41 -0600 Subject: [PATCH 2/7] Tests for (Mutable)BinaryHeap with custom ordering --- test/test_binheap.jl | 48 +++++++++++++++++++++++++++++++++++- test/test_mutable_binheap.jl | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/test/test_binheap.jl b/test/test_binheap.jl index a82f633f5..65ad49f5f 100644 --- a/test/test_binheap.jl +++ b/test/test_binheap.jl @@ -4,6 +4,8 @@ @testset "make heap" begin vs = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7] + vs2 = collect(enumerate(vs)) + ordering = Base.Order.By(last) @testset "construct heap" begin BinaryHeap{Int, Base.ForwardOrdering}() @@ -20,6 +22,10 @@ BinaryMaxHeap{Int}(vs) BinaryMaxHeap(vs) + BinaryHeap{eltype(vs2)}(ordering) + BinaryHeap{eltype(vs2)}(ordering, vs2) + BinaryHeap(ordering, vs2) + @test true end @@ -27,6 +33,7 @@ BinaryHeap{Float64, Base.ForwardOrdering}(vs) BinaryMinHeap{Float64}(vs) BinaryMaxHeap{Float64}(vs) + BinaryHeap{Tuple{Int, Float64}}(ordering, vs2) @test true end @@ -61,6 +68,16 @@ @test sizehint!(h, 100) === h end + @testset "make custom ordering heap" begin + h = BinaryHeap(ordering, vs2) + + @test length(h) == 10 + @test !isempty(h) + @test first(h) == (2, 1) + @test isheap([(2, 1), (4, 2), (3, 3), (1, 4), (10, 7), (6, 9), (7, 10), (8, 14), (9, 8), (5, 16)], ordering) + @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)) @@ -95,7 +112,6 @@ @test isequal(extract_all!(hmin), [1, 2, 3, 4, 7, 8, 9, 10, 14, 16]) @test isempty(hmin) end - end @testset "push! hmax" begin @@ -127,6 +143,36 @@ @test isempty(hmax) end end + + @testset "push! custom ordering" begin + heap = BinaryHeap{Tuple{Int,Int}}(ordering) + @test length(heap) == 0 + @test isempty(heap) + + ss = Any[ + [(1, 4)], + [(2, 1), (1, 4)], + [(2, 1), (1, 4), (3, 3)], + [(2, 1), (4, 2), (3, 3), (1, 4)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10), (8, 14)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10), (8, 14), (9, 8)], + [(2, 1), (4, 2), (3, 3), (1, 4), (10, 7), (6, 9), (7, 10), (8, 14), (9, 8), (5, 16)]] + + for i = 1 : length(vs2) + push!(heap, vs2[i]) + @test length(heap) == i + @test !isempty(heap) + @test isequal(heap.valtree, ss[i]) + end + + @testset "pop! custom ordering" begin + @test isequal(extract_all!(heap), sort(vs2, order=ordering)) + @test isempty(heap) + end + end end end diff --git a/test/test_mutable_binheap.jl b/test/test_mutable_binheap.jl index 064192f19..1cf5fcbf5 100644 --- a/test/test_mutable_binheap.jl +++ b/test/test_mutable_binheap.jl @@ -53,6 +53,8 @@ end @testset "MutableBinheap" begin vs = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7] + vs2 = collect(enumerate(vs)) + ordering = Base.Order.By(last) @testset "construct heap" begin MutableBinaryHeap{Int, Base.ForwardOrdering}() @@ -69,6 +71,10 @@ end MutableBinaryMaxHeap{Int}(vs) MutableBinaryMaxHeap(vs) + MutableBinaryHeap{eltype(vs2)}(ordering) + MutableBinaryHeap{eltype(vs2)}(ordering, vs2) + MutableBinaryHeap(ordering, vs2) + @test true end @@ -103,6 +109,17 @@ end @test sizehint!(h, 100) === h end + @testset "make mutable binary custom ordering heap" begin + h = MutableBinaryHeap(ordering, vs2) + + @test length(h) == 10 + @test !isempty(h) + @test first(h) == (2, 1) + @test isequal(list_values(h), vs2) + @test isequal(heap_values(h), [(2, 1), (4, 2), (3, 3), (1, 4), (10, 7), (6, 9), (7, 10), (8, 14), (9, 8), (5, 16)]) + @test sizehint!(h, 100) === h + end + @testset "hmin / push! / pop!" begin hmin = MutableBinaryMinHeap{Int}() @test length(hmin) == 0 @@ -163,7 +180,37 @@ end # test pop! @test isequal(extract_all!(hmax), [16, 14, 10, 9, 8, 7, 4, 3, 2, 1]) @test isempty(hmax) + end + @testset "Custom ordering push! / pop!" begin + heap = MutableBinaryHeap{Tuple{Int,Int}}(ordering) + @test length(heap) == 0 + @test isempty(heap) + + # test push! + ss = Any[ + [(1, 4)], + [(2, 1), (1, 4)], + [(2, 1), (1, 4), (3, 3)], + [(2, 1), (4, 2), (3, 3), (1, 4)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10), (8, 14)], + [(2, 1), (4, 2), (3, 3), (1, 4), (5, 16), (6, 9), (7, 10), (8, 14), (9, 8)], + [(2, 1), (4, 2), (3, 3), (1, 4), (10, 7), (6, 9), (7, 10), (8, 14), (9, 8), (5, 16)]] + for i = 1 : length(vs2) + ia = push!(heap, vs2[i]) + @test ia == i + @test length(heap) == i + @test !isempty(heap) + @test isequal(list_values(heap), vs2[1:i]) + @test isequal(heap_values(heap), ss[i]) + end + + # test pop! + @test isequal(extract_all!(heap), sort(vs2, order=ordering)) + @test isempty(heap) end @testset "hybrid push! and pop!" begin From 09719f543153308398467ee8841335a7e87dfa2f Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Tue, 1 Sep 2020 14:25:33 -0600 Subject: [PATCH 3/7] by and lt arguments to nlargest/nsmallest --- src/heaps.jl | 28 ++++++++++++++++++---------- test/test_binheap.jl | 12 ++++++++++-- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/heaps.jl b/src/heaps.jl index 25e12243e..3ab8e7570 100644 --- a/src/heaps.jl +++ b/src/heaps.jl @@ -129,37 +129,45 @@ function nextreme(ord::Base.Ordering, n::Int, arr::AbstractVector{T}) where T end """ - nlargest(n, arr) + nlargest(n, arr; kw...) Return the `n` largest elements of the array `arr`. Equivalent to: - sort(arr, order = Base.Reverse)[1:min(n, end)] + sort(arr, kw..., rev=true)[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. +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) - return nextreme(Base.Reverse, n, arr) +function nlargest(n::Int, arr::AbstractVector; lt=isless, by=identity) + order = Base.ReverseOrdering(Base.ord(lt, by, nothing)) + return nextreme(order, n, arr) end """ - nsmallest(n, arr) + nsmallest(n, arr; kw...) Return the `n` smallest elements of the array `arr`. Equivalent to: - sort(arr, order = Base.Forward)[1:min(n, end)] + sort(arr; kw...)[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. +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) - return nextreme(Base.Forward, n, arr) +function nsmallest(n::Int, arr::AbstractVector; lt=isless, by=identity) + order = Base.ord(lt, by, nothing) + return nextreme(order, n, arr) end diff --git a/test/test_binheap.jl b/test/test_binheap.jl index 65ad49f5f..c6b13e498 100644 --- a/test/test_binheap.jl +++ b/test/test_binheap.jl @@ -208,9 +208,17 @@ 102,-56,-17,-41,25,-30,-84,26,-84,48,49,-5,-38,28, 114,-54,96,-55,67,74,127,-61,124,11,-7,93,-51,110, -106,-84,-90,-18,-12,-116,21,115,50] + square = x -> x^2 + 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) + r = 1:min(n, length(ss)) + + @test nlargest(n, ss) == sort(ss, rev=true)[r] + @test nsmallest(n, ss) == sort(ss)[r] + + @test nlargest(n, ss, by=square) == sort(ss, by=square, rev=true)[r] + @test nsmallest(n, ss, by=square) == sort(ss, by=square)[r] + @test nlargest(n, ss) == DataStructures.nextreme(DataStructures.FasterReverse(), n, ss) @test nsmallest(n, ss) == DataStructures.nextreme(DataStructures.FasterForward(), n, ss) end From 5d2defbc69f507b969c06d88af7fd4ceee5c01ed Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Tue, 1 Sep 2020 14:28:47 -0600 Subject: [PATCH 4/7] Update docs on custom ordering in heaps --- docs/src/heaps.md | 76 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/docs/src/heaps.md b/docs/src/heaps.md index 162c165ac..e442e01ae 100644 --- a/docs/src/heaps.md +++ b/docs/src/heaps.md @@ -61,19 +61,32 @@ 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. +## Using alternate orderings + +Heaps can also use alternate orderings apart from the default one defined by +`Base.isless`. This is accomplished by passing an instance of `Base.Ordering` +as the first argument to the constructor. The top of the heap will then be the +element that comes first according to this ordering. + +The following example uses 2-tuples to track the index of each element in the +original array, but sorts only by the data value: + ```julia -h = BinaryHeap{Float64, DataStructures.FasterForward}() # faster min heap -h = BinaryHeap{Float64, DataStructures.FasterReverse}() # faster max heap +data = collect(enumerate(["foo", "bar", "baz"])) -h = MutableBinaryHeap{Float64, DataStructures.FasterForward}() # faster mutable min heap -h = MutableBinaryHeap{Float64, DataStructures.FasterReverse}() # faster mutable max heap +h1 = BinaryHeap(data) # Standard lexicographic ordering for tuples +first(h1) # => (1, "foo") -h = BinaryHeap{MyStruct, MyStructOrdering}() # heap containing custom struct +h2 = BinaryHeap(Base.By(last), data) # Order by 2nd element only +first(h2) # => (2, "bar") +``` + +If the ordering type is a singleton it can be passed as a type parameter to the +constructor instead: + +```julia +BinaryHeap{T, O}() # => BinaryHeap{T}(O()) +MutableBinaryHeap{T, O}() # => MutableBinaryHeap{T}(O()) ``` ## Min-max heaps @@ -81,6 +94,7 @@ Min-max heaps maintain the minimum _and_ the maximum of a set, allowing both to be retrieved in constant (`O(1)`) time. The min-max heaps in this package are subtypes of `AbstractMinMaxHeap <: AbstractHeap` and have the same interface as other heaps with the following additions: + ```julia # Let h be a min-max heap, k an integer minimum(h) # return the smallest element @@ -95,6 +109,7 @@ popmax!(h, k) # remove and return the largest k elements popall!(h) # remove and return all the elements, sorted smallest to largest popall!(h, o) # remove and return all the elements according to ordering o ``` + The usual `first(h)` and `pop!(h)` are defined to be `minimum(h)` and `popmin!(h)`, respectively. @@ -115,13 +130,42 @@ Heaps can be used to extract the largest or smallest elements of an array without sorting the entire array first: ```julia -nlargest(3, [0,21,-12,68,-25,14]) # => [68,21,14] -nsmallest(3, [0,21,-12,68,-25,14]) # => [-25,-12,0] +data = [0,21,-12,68,-25,14] +nlargest(3, data) # => [68,21,14] +nsmallest(3, data) # => [-25,-12,0] +``` + +Both methods also support the `by` and `lt` keywords to customize the sort order, +as in `Base.sort`: + +```julia +nlargest(3, data, by=x -> x^2) # => [68,-25,21] +nsmallest(3, data, by=x -> x^2) # => [0,-12,14] ``` -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. +The lower-level `DataStructures.nextreme` function takes a `Base.Ordering` +instance as the first argument and returns the first `n` elements according to +this ordering: + +```julia +DataStructures.nextreme(Base.Forward, n, a) # Equivalent to nsmallest(n, a) ``` -DataStructures.nextreme(DataStructures.FasterReverse(), n, a) # faster nlargest(n, a) -DataStructures.nextreme(DataStructures.FasterForward(), n, a) # faster nsmallest(n, a) + + +# Improving performance with Float data + +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 and may achive a 2x performance boost: + +```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 + +DataStructures.nextreme(DataStructures.FasterReverse(), n, a) # faster nlargest(n, a) +DataStructures.nextreme(DataStructures.FasterForward(), n, a) # faster nsmallest(n, a) ``` From e4fc1cd378636bf72bb8bdf32c94c01eb6017b2e Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Tue, 1 Sep 2020 14:28:19 -0600 Subject: [PATCH 5/7] heaps docs code block comment alignment --- docs/src/heaps.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/heaps.md b/docs/src/heaps.md index e442e01ae..4a78ac587 100644 --- a/docs/src/heaps.md +++ b/docs/src/heaps.md @@ -33,13 +33,13 @@ 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 +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) +update!(h, i, v) # updates the value of an element (referred to by the handle i) -delete!(h, i) # deletes the node with handle i from the heap +delete!(h, i) # deletes the node with handle i from the heap -v, i = top_with_handle(h) # returns the top value of a heap and its handle +v, i = top_with_handle(h) # returns the top value of a heap and its handle ``` Currently, both min/max versions of binary heap (type `BinaryHeap`) and @@ -49,16 +49,16 @@ Examples of constructing a heap: ```julia h = BinaryMinHeap{Int}() -h = BinaryMaxHeap{Int}() # create an empty min/max binary heap of integers +h = BinaryMaxHeap{Int}() # create an empty min/max binary heap of integers h = BinaryMinHeap([1,4,3,2]) -h = BinaryMaxHeap([1,4,3,2]) # create a min/max heap from a vector +h = BinaryMaxHeap([1,4,3,2]) # create a min/max heap from a vector h = MutableBinaryMinHeap{Int}() -h = MutableBinaryMaxHeap{Int}() # create an empty mutable min/max heap +h = MutableBinaryMaxHeap{Int}() # create an empty mutable min/max heap h = MutableBinaryMinHeap([1,4,3,2]) -h = MutableBinaryMaxHeap([1,4,3,2]) # create a mutable min/max heap from a vector +h = MutableBinaryMaxHeap([1,4,3,2]) # create a mutable min/max heap from a vector ``` ## Using alternate orderings @@ -119,7 +119,7 @@ This package includes an implementation of a binary min-max heap (`BinaryMinMaxH Examples: ```julia -h = BinaryMinMaxHeap{Int}() # create an empty min-max heap with integer values +h = BinaryMinMaxHeap{Int}() # create an empty min-max heap with integer values h = BinaryMinMaxHeap([1, 2, 3, 4]) # create a min-max heap from a vector ``` From fb0f2af5da996fa64f4ef1188dbeb53c5c94e190 Mon Sep 17 00:00:00 2001 From: Jared Lumpe Date: Thu, 10 Sep 2020 09:47:19 -0600 Subject: [PATCH 6/7] Increment patch version no --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index aaffcaa15..6490357f7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataStructures" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.2" +version = "0.18.3" [deps] Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" From 1727f11ace80d26b64603d1681d738011a37b0b3 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Sun, 13 Sep 2020 18:58:38 +0100 Subject: [PATCH 7/7] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6490357f7..a3d3f358c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DataStructures" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.3" +version = "0.18.5" [deps] Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"