From b4e6baab353b034ad24798410d9c2b919c96cf26 Mon Sep 17 00:00:00 2001 From: Gerhard Aigner Date: Thu, 12 Oct 2023 08:27:30 +0200 Subject: [PATCH] refactor MutableLinkedList --- docs/src/mutable_linked_list.md | 9 +- src/deprecations.jl | 12 + src/mutable_list.jl | 296 ++++++++++++++++------- test/test_mutable_list.jl | 410 ++++++++++++++++++++++++++------ 4 files changed, 562 insertions(+), 165 deletions(-) diff --git a/docs/src/mutable_linked_list.md b/docs/src/mutable_linked_list.md index fd900e767..17eaf7b85 100644 --- a/docs/src/mutable_linked_list.md +++ b/docs/src/mutable_linked_list.md @@ -10,6 +10,7 @@ Usage: l = MutableLinkedList{T}() # initialize an empty list of type T l = MutableLinkedList{T}(elts...) # initialize a list with elements of type T isempty(l) # test whether list is empty +empty!(l) # empties the list length(l) # get the number of elements in list collect(l) # return a vector consisting of list elements eltype(l) # return type of list @@ -18,19 +19,23 @@ last(l) # return value of last element of list l1 == l2 # test lists for equality map(f, l) # return list with f applied to elements filter(f, l) # return list of elements where f(el) == true +filter!(f, l) # mutating version of `filter(f,l)` reverse(l) # return reversed list +reverse!(l) # reverse the list in place copy(l) # return a copy of list getindex(l, idx) || l[idx] # get value at index getindex(l, range) || l[range] # get values within range a:b setindex!(l, data, idx) # set value at index to data -append!(l1, l2) # attach l2 at the end of l1 -append!(l, elts...) # attach elements at end of list +append!(l, collections...) # all elements of all collections to l +prepend(l, collections...) # add all elements of all collections in front of l delete!(l, idx) # delete element at index delete!(l, range) # delete elements within range a:b push!(l, data) # add element to end of list pushfirst!(l, data) # add element to beginning of list pop!(l) # remove element from end of list popfirst!(l) # remove element from beginning of list +splice!(l,idx,ins) # remove element given as `idx` and insert elements of `ins` instead. idx is either an Int or a UnitRange + ``` `MutableLinkedList` implements the Iterator interface, iterating over the list diff --git a/src/deprecations.jl b/src/deprecations.jl index 78eac122c..48bc69e52 100644 --- a/src/deprecations.jl +++ b/src/deprecations.jl @@ -23,6 +23,18 @@ Base.@deprecate_binding IntDisjointSets IntDisjointSet @deprecate insert!(m::SortedDict, k, d) push_return_semitoken!(m::SortedDict, k=>d) @deprecate insert!(m::SortedMultiDict, k, d) (push_return_semitoken!(m::SortedMultiDict, k=>d))[2] +function Base.delete!(l::MutableLinkedList, idx) + Expr(:meta, :noinline) + Base.depwarn("`delete!(l::MutableLinkedList,idx::Int)` is deprecated, use `deleteat!(l,idx)` instead.", :delete!) + deleteat!(l,idx) +end + +function Base.delete!(l::MutableLinkedList, r::UnitRange) + Expr(:meta, :noinline) + Base.depwarn("`delete!(l::MutableLinkedList,r::UnitRange)` is deprecated, use `deleteat!(l,idx)` instead.", :delete!) + deleteat!(l,r) +end + function Base.peek(q::PriorityQueue) Expr(:meta, :noinline) Base.depwarn("`peek(q::PriorityQueue)` is deprecated, use `first(q)` instead.", :peek) diff --git a/src/mutable_list.jl b/src/mutable_list.jl index 788a52d3a..8f564cc00 100644 --- a/src/mutable_list.jl +++ b/src/mutable_list.jl @@ -2,16 +2,14 @@ mutable struct ListNode{T} data::T prev::ListNode{T} next::ListNode{T} - function ListNode{T}() where T + function ListNode{T}() where {T} node = new{T}() - node.next = node - node.prev = node + node.next = node.prev = node return node end - function ListNode{T}(prev,data,next) where T - node = new{T}(data) - node.next = next - node.prev = prev + function ListNode{T}(prev, data, next) where {T} + node = new{T}(data, prev, next) + prev.next = next.prev = node return node end end @@ -19,13 +17,13 @@ end mutable struct MutableLinkedList{T} len::Int node::ListNode{T} - function MutableLinkedList{T}() where T + function MutableLinkedList{T}() where {T} return new{T}(0, ListNode{T}()) end end MutableLinkedList() = MutableLinkedList{Any}() - +MutableLinkedList(elts...) = MutableLinkedList{eltype(elts)}(elts...) MutableLinkedList{T}(elts...) where {T} = append!(MutableLinkedList{T}(), elts) Base.iterate(l::MutableLinkedList) = l.len == 0 ? nothing : (l.node.next.data, l.node.next.next) @@ -33,23 +31,58 @@ Base.iterate(l::MutableLinkedList, n::ListNode) = n === l.node ? nothing : (n.da Base.isempty(l::MutableLinkedList) = l.len == 0 Base.length(l::MutableLinkedList) = l.len -Base.collect(l::MutableLinkedList{T}) where T = T[x for x in l] -Base.eltype(::Type{<:MutableLinkedList{T}}) where T = T +Base.collect(l::MutableLinkedList{T}) where {T} = T[x for x in l] +Base.eltype(::Type{<:MutableLinkedList{T}}) where {T} = T Base.lastindex(l::MutableLinkedList) = l.len +_boundscheck(l, idx) = 0 < idx <= l.len || throw(BoundsError(l, idx)) + +# mend two nodes together +nconnect(a::ListNode{T}, b::ListNode{T}) where {T} = a.next, b.prev = b, a + +# traverse to idx either from front or back, depending on whats faster +function ntraverse(l::MutableLinkedList, idx::Int) + n = length(l) + node = l.node + if idx < n ÷ 2 + for _ in 1:idx + node = node.next + end + else + for _ in 1:(n-idx+1) + node = node.prev + end + end + return node +end + +# remove node from the list, the node itself is unchanged +function nremove(l::MutableLinkedList, node::ListNode) + nconnect(node.prev, node.next) + l.len -= 1 + return node +end + +# create a new node, insert it after the provided `node` and return it +function ninsert(l::MutableLinkedList{T}, node::ListNode{T}, data) where {T} + ins = ListNode{T}(node, data, node.next) + l.len += 1 + return ins +end + function Base.first(l::MutableLinkedList) - isempty(l) && throw(ArgumentError("List is empty")) + _boundscheck(l, 1) return l.node.next.data end function Base.last(l::MutableLinkedList) - isempty(l) && throw(ArgumentError("List is empty")) + _boundscheck(l, length(l)) return l.node.prev.data end Base.:(==)(l1::MutableLinkedList{T}, l2::MutableLinkedList{S}) where {T,S} = false -function Base.:(==)(l1::MutableLinkedList{T}, l2::MutableLinkedList{T}) where T +function Base.:(==)(l1::MutableLinkedList{T}, l2::MutableLinkedList{T}) where {T} length(l1) == length(l2) || return false for (i, j) in zip(l1, l2) i == j || return false @@ -57,7 +90,7 @@ function Base.:(==)(l1::MutableLinkedList{T}, l2::MutableLinkedList{T}) where T return true end -function Base.map(f::Base.Callable, l::MutableLinkedList{T}) where T +function Base.map(f::Base.Callable, l::MutableLinkedList{T}) where {T} if isempty(l) && f isa Function S = Core.Compiler.return_type(f, Tuple{T}) return MutableLinkedList{S}() @@ -80,17 +113,15 @@ function Base.map(f::Base.Callable, l::MutableLinkedList{T}) where T end end -function Base.filter(f::Function, l::MutableLinkedList{T}) where T +function Base.filter(f::Function, l::MutableLinkedList{T}) where {T} l2 = MutableLinkedList{T}() for h in l - if f(h) - push!(l2, h) - end + f(h) && push!(l2, h) end return l2 end -function Base.reverse(l::MutableLinkedList{T}) where T +function Base.reverse(l::MutableLinkedList{T}) where {T} l2 = MutableLinkedList{T}() for h in l pushfirst!(l2, h) @@ -98,118 +129,211 @@ function Base.reverse(l::MutableLinkedList{T}) where T return l2 end -Base.copy(l::MutableLinkedList{T}) where T = append!(MutableLinkedList{T}(), l) +function Base.filter!(f::Function, l::MutableLinkedList{T}) where {T} + node = l.node.next + for _ in 1:length(l) + f(node.data) || nremove(l, node) + node = node.next + end + return l +end -function _traverse(l::MutableLinkedList, idx::Int) +function Base.reverse!(l::MutableLinkedList) node = l.node - for _ in 1:idx - node = node.next + for _ in 1:length(l)+1 + node.next, node.prev = node.prev, node.next + node = node.prev end - return node + return l end +Base.copy(l::MutableLinkedList{T}) where {T} = append!(MutableLinkedList{T}(), l) + function Base.getindex(l::MutableLinkedList, idx::Int) - @boundscheck 0 < idx <= l.len || throw(BoundsError(l, idx)) - return _traverse(l, idx).data + _boundscheck(l, idx) + return ntraverse(l, idx).data end -function Base.getindex(l::MutableLinkedList{T}, r::UnitRange) where T - @boundscheck 0 < first(r) <= last(r) <= l.len || throw(BoundsError(l, r)) +function Base.getindex(l::MutableLinkedList{T}, r::UnitRange) where {T} + n = length(r) l2 = MutableLinkedList{T}() - node = _traverse(l, first(r)) - len = length(r) - for _ in 1:len - push!(l2, node.data) - node = node.next + if n > 0 + @boundscheck 0 < first(r) <= last(r) <= l.len || throw(BoundsError(l, r)) + node = ntraverse(l, first(r)) + for _ in 1:n + push!(l2, node.data) + node = node.next + end end return l2 end -function Base.setindex!(l::MutableLinkedList{T}, data, idx::Int) where T - @boundscheck 0 < idx <= l.len || throw(BoundsError(l, idx)) - _traverse(l, idx).data = convert(T, data) +function Base.setindex!(l::MutableLinkedList{T}, data, idx::Int) where {T} + _boundscheck(l, idx) + ntraverse(l, idx).data = convert(T, data) return l end -function Base.append!(l::MutableLinkedList, itr) - for e in itr - push!(l, e) +function Base.append!(l::MutableLinkedList, collections...) + node = l.node.prev + for c in collections + for e in c + node = ninsert(l, node, e) + end end return l end -Base.append!(l::MutableLinkedList, elts...) = append!(l, elts) +function Base.prepend!(l::MutableLinkedList, collections...) + node = l.node + for c in collections + for e in c + node = ninsert(l, node, e) + end + end + return l +end -function Base.delete!(l::MutableLinkedList, idx::Int) - @boundscheck 0 < idx <= l.len || throw(BoundsError(l, idx)) - node = _traverse(l, idx) - prev = node.prev - next = node.next - prev.next = next - next.prev = prev - l.len -= 1 +function Base.deleteat!(l::MutableLinkedList, idx::Int) + _boundscheck(l, idx) + nremove(l, ntraverse(l, idx)) return l end -function Base.delete!(l::MutableLinkedList, r::UnitRange) - @boundscheck 0 < first(r) <= last(r) <= l.len || throw(BoundsError(l, r)) - node = _traverse(l, first(r)) - prev = node.prev - len = length(r) - for _ in 1:len +function Base.deleteat!(l::MutableLinkedList, r::UnitRange) + n = length(r) + if n > 0 + 0 < first(r) <= last(r) <= l.len || throw(BoundsError(l, r)) + end + node = ntraverse(l, first(r)) + for _ in 1:n + nremove(l, node) node = node.next end - next = node - prev.next = next - next.prev = prev - l.len -= len return l end -function Base.push!(l::MutableLinkedList{T}, data) where T - oldlast = l.node.prev - node = ListNode{T}(oldlast, data, l.node) - l.node.prev = node - oldlast.next = node - l.len += 1 +function Base.push!(l::MutableLinkedList{T}, data) where {T} + ninsert(l, l.node.prev, data) return l end -function Base.pushfirst!(l::MutableLinkedList{T}, data) where T - oldfirst = l.node.next - node = ListNode{T}(l.node, data, oldfirst) - l.node.next = node - oldfirst.prev = node - l.len += 1 +function Base.pushfirst!(l::MutableLinkedList{T}, data) where {T} + ninsert(l, l.node, data) return l end function Base.pop!(l::MutableLinkedList) isempty(l) && throw(ArgumentError("List must be non-empty")) - last = l.node.prev.prev - data = l.node.prev.data - last.next = l.node - l.node.prev = last - l.len -= 1 - return data + node = l.node.prev + nremove(l, node) + return node.data end function Base.popfirst!(l::MutableLinkedList) isempty(l) && throw(ArgumentError("List must be non-empty")) - first = l.node.next.next - data = l.node.next.data - first.prev = l.node - l.node.next = first - l.len -= 1 + node = l.node.next + nremove(l, node) + return node.data +end + +function Base.popat!(l::MutableLinkedList, idx::Int) + _boundscheck(l, idx) + node = ntraverse(l, idx) + nremove(l, node) + return node.data +end + +function Base.popat!(l::MutableLinkedList, idx::Int, default) + return (0 < idx <= l.len) ? popat!(l, idx) : default +end + +function Base.insert!(l::MutableLinkedList{T}, idx::Int, data) where {T} + # special case length+1 for insert index to allow adding to the end + 0 < idx <= l.len + 1 || throw(BoundsError(l, idx)) + ninsert(l, ntraverse(l, idx - 1), data) + return l +end + +function Base.splice!(l::MutableLinkedList{T}, idx::Int, ins=T[]) where {T} + _boundscheck(l, idx) + node = ntraverse(l, idx) + data = node.data + nremove(l, node) + node = node.prev + for e in ins + node = ninsert(l, node, e) + end return data end +function Base.splice!(l::MutableLinkedList{T}, r::AbstractUnitRange{<:Integer}, ins=T[]) where {T} + n = length(r) + if n == 0 && !isempty(ins) + 0 < first(r) <= l.len + 1 || throw(BoundsError(l, r)) + elseif n == 1 + return splice!(l, first(r), ins) + elseif n > 1 + 0 < first(r) <= last(r) <= l.len || throw(BoundsError(l, r)) + end + l2 = MutableLinkedList{T}() + # determine nodes to splice + splice_first_node = splice_last_node = ntraverse(l, first(r)) + for _ in 1:(n-1) + splice_last_node = splice_last_node.next + end + # node to insert is ahead of the splice region + insert_node = splice_first_node.prev + if n != 0 + # link the nodes surrounding the splice region + nconnect(splice_first_node.prev, splice_last_node.next) + l.len -= n + # rewire the spliced region to l2 + nconnect(l2.node, splice_first_node) + nconnect(splice_last_node, l2.node) + l2.len = n + end + for e in ins + insert_node = ninsert(l, insert_node, e) + end + return l2 +end + +function Base.empty!(l::MutableLinkedList) + l.node.next = l.node.prev = l.node + l.len = 0 + return l +end + function Base.show(io::IO, node::ListNode) - print(io, typeof(node), "(", node.data, ")") + if get(io, :compact, false) + print(io, node.data) + else + print(io, typeof(node), "(", node.data, ")") + end end function Base.show(io::IO, l::MutableLinkedList) - print(io, typeof(l), '(') - join(io, l, ", ") - print(io, ')') + rows, _ = displaysize(io) + n = length(l) + println(io, n, "-element ", typeof(l), ":") # imitate vector + rows -= 4 + if get(io, :limit, false) && n > rows + fromstart = cld(rows, 2) + for i in 1:fromstart + show(io, l[i]) + print(io, '\n') + end + println(io, '⋮') + fromend = n - fld(rows, 2) + 1 + for i in fromend:n + show(io, l[i]) + i != n && print(io, '\n') + end + else + for i in 1:n + show(io, l[i]) + i != n && print(io, '\n') + end + end end diff --git a/test/test_mutable_list.jl b/test/test_mutable_list.jl index 7ba1db5b0..db5c6e630 100644 --- a/test/test_mutable_list.jl +++ b/test/test_mutable_list.jl @@ -1,3 +1,5 @@ + +m(x...) = MutableLinkedList{Int}(x...) @testset "MutableLinkedList" begin @testset "empty list" begin @@ -14,9 +16,301 @@ @test_throws ArgumentError popfirst!(l1) end + @testset "empty!" begin + l = MutableLinkedList(1, 2, 3) + @test !isempty(l) + @test empty!(l) == MutableLinkedList{Int}() + @test isempty(l) + @test l == MutableLinkedList{Int}() + @test length(l) == 0 + @test l.node == l.node.next == l.node.prev + end + + @testset "show" begin + l = MutableLinkedList{Int32}(1:2...) + io = IOBuffer() + @test sprint(io -> show(io, l.node.next)) == "$(typeof(l.node.next))($(l.node.next.data))" + @test sprint(show, MutableLinkedList{Int32}()) == """ + 0-element MutableLinkedList{Int32}: + """ + @test sprint(show, MutableLinkedList{Int32}(1:2...)) == """ + 2-element MutableLinkedList{Int32}: + 1 + 2""" + @test sprint(show, MutableLinkedList{Int32}(1)) == """ + 1-element MutableLinkedList{Int32}: + 1""" + @test sprint(show, MutableLinkedList{UInt8}(1:2...)) == """ + 2-element MutableLinkedList{UInt8}: + 0x01 + 0x02""" + l = MutableLinkedList{Int}(1:1000...) + p = sprint(show, l; context=(:limit => true, :displaysize => (10, 10))) + @test p == """1000-element MutableLinkedList{$(string(Int))}: + 1 + 2 + 3 + ⋮ + 998 + 999 + 1000""" + p = sprint(show, l; context=(:limit => true, :displaysize => (9, 10))) + @test p == """1000-element MutableLinkedList{$(string(Int))}: + 1 + 2 + 3 + ⋮ + 999 + 1000""" + iob = IOBuffer() + print(iob, "1000-element MutableLinkedList{$(string(Int))}:\n") + for i in 1:1000 + print(iob, i, i != 1000 ? "\n" : "") + end + p = sprint(show, l; context=(:limit => false, :displaysize => (9, 10))) + @test p == String(take!(iob)) + end + + @testset "append!" begin + l = m() + @test append!(l, []) == m() + @test append!(l, [1]) == m(1) + @test append!(l, [2, 3]) == m(1, 2, 3) + @test append!(l, [4, 5], [], [6, 7]) == m(1, 2, 3, 4, 5, 6, 7) + @test l == m(1, 2, 3, 4, 5, 6, 7) + l1 = m(4, 5, 6) + l2 = m(7, 8, 9) + @test append!(l1, l2) == m(4, 5, 6, 7, 8, 9) + @test l2 == m(7, 8, 9) # append! should not mutate other arguments + end + @testset "prepend!" begin + l = m() + @test prepend!(l, []) == m() + @test prepend!(l, [1]) == m(1) + @test prepend!(l, [3, 2]) == m(3, 2, 1) + @test prepend!(l, [7, 6], [], [5, 4]) == m(7, 6, 5, 4, 3, 2, 1) + @test l == m(7, 6, 5, 4, 3, 2, 1) + l1 = m(4, 5, 6) + l2 = m(7, 8, 9) + @test prepend!(l2, l1) == m(4, 5, 6, 7, 8, 9) + @test l1 == m(4, 5, 6) # prepend! should not mutate other arguments + end + + @testset "copy" begin + l = m() + @test lc = copy(l) == m() + l = m(10, 20) + lc = copy(l) + @test lc == l + @test lc == m(10, 20) + @test lc !== l + @test lc.node !== l.node + @test lc.node.next !== l.node.next # 10 + @test lc.node.next.next !== l.node.next # 20 + @test lc.node.next.next.next !== l.node # back again + @test lc.node.prev !== l.node.prev # 20 + @test lc.node.prev.prev !== l.node.prev # 10 + @test lc.node.prev.prev.prev !== l.node # back again + @test length(l) == length(lc) + end + @testset "reverse" begin + @test m() == reverse(m()) + l1 = m(1, 2, 3) + l2 = reverse(m(3, 2, 1)) + @test l1 == l2 + l1 = m(1, 2, 3) + l2 = reverse(l1) + @test l1.node !== l2.node + l1[3] = 33 + l2[2] = 22 + @test l1 == m(1, 2, 33) + @test l2 == m(3, 22, 1) # check for aliasing + end + + @testset "reverse!" begin + @test m() == reverse!(m()) + l1 = m(1, 2, 3) + l2 = reverse!(l1) + @test l1 === l2 + @test l1 == m(3, 2, 1) + @test reverse!(m(1)) == m(1) + @test reverse!(m(1, 2)) == m(2, 1) + end + + @testset "deleteat!" begin + @test_throws BoundsError deleteat!(m(), 0) + @test_throws BoundsError deleteat!(m(1, 2, 3), 100) + l = m(1, 2, 3, 4) + @test deleteat!(l, 1) == m(2, 3, 4) + @test deleteat!(l, 3) == m(2, 3) + @test deleteat!(l, 2) == m(2) + @test deleteat!(l, 1) == m() + end + + @testset "map" begin + l = m(1, 2, 3) + @test map(x -> 2x, l) == m(2, 4, 6) + l = m(1, 2, 3) + lm = map(x -> 2x % UInt8, l) + @test lm == MutableLinkedList{UInt8}(0x02, 0x04, 0x06) + @test eltype(lm) == UInt8 + @test map(abs, m(1, -2, 3)) == m(1, 2, 3) + l = m() + lm = map(abs, l) + @test lm == l + @test lm !== l + @test map(string, m(1, 2, 3)) == MutableLinkedList{String}("1", "2", "3") + l = m(1, 2, 3, 4) + f(x) = x % 2 == 0 ? convert(Int8, x) : convert(Float16, x) + @test typeof(map(f, l)) == MutableLinkedList{Real} + end + + @testset "filter" begin + l = m(1, 2, 3) + @test filter(iseven, l) == m(2) + @test filter(isodd, l) == m(1, 3) + @test filter(isodd, m()) == m() + end + + @testset "filter!" begin + l = m(1, 2, 3, 4, 5) + filter!(iseven, l) + @test l == m(2, 4) + @test l !== m(2, 4) + filter!(isodd, l) + @test l == m() + l = m() + @test filter!(isodd, l) == m() + @test filter!(x -> x < 0, m(1, 2, 3)) == m() + @test filter!(x -> x > 0, m(1, 2, 3)) == m(1, 2, 3) + end + + @testset "splice! with no replacement" begin + l = m(1, 2, 3) + @test splice!(l, 2) == 2 + @test l == m(1, 3) + @test splice!(l, 1) == 1 + @test l == m(3) + @test splice!(l, 1) == 3 + @test l == m() + + l = m(1:10...) + @test splice!(l, 8:10) == m(8, 9, 10) + @test l == m(1:7...) + @test splice!(l, 1:3) == m(1, 2, 3) + @test l == m(4, 5, 6, 7) + @test splice!(l, 1:0) == m() + @test l == m(4, 5, 6, 7) + @test splice!(l, 1:1) == 4 + @test l == m(5, 6, 7) + + @test_throws BoundsError splice!(m(), 1) + @test_throws BoundsError splice!(m(), 1:1) + end + @testset "splice! with replacement" begin + l = m(1, 2, 3) + @test splice!(l, 1, 11) == 1 + @test l == m(11, 2, 3) + @test splice!(l, 2, 22) == 2 + @test l == m(11, 22, 3) + @test splice!(l, 3, 33) == 3 + @test l == m(11, 22, 33) + @test splice!(l, 2:3, [222, 333]) == m(22, 33) + @test l == m(11, 222, 333) + @test splice!(l, 4:0, [444]) == m() + @test l == m(11, 222, 333, 444) + @test splice!(l, 1:4, []) == m(11, 222, 333, 444) + @test l == m() + @test splice!(l, 1:0, [7, 8, 9]) == m() + @test l == m(7, 8, 9) + @test splice!(l, 4:0, [10, 11]) == m() + @test l == m(7, 8, 9, 10, 11) + @test splice!(l, 5:5, [111, 112]) == 11 + @test l == m(7, 8, 9, 10, 111, 112) + end + + + + @testset "fuzzed comparison against `Vector`" begin + + function compare!(v, l, f, args...) + vres = try + f(v, args...) + catch e + e + end + lres = try + f(l, args...) + catch e + e + end + VT, LT = typeof(vres), typeof(lres) + if (VT <: Exception) ⊻ (LT <: Exception) + @info "compare!" v, l, f, args + end + @test !((VT <: Exception) ⊻ (LT <: Exception)) + + if VT <: Exception + @test VT == LT + else + + @test all(vres .== lres) + @test all(v .== l) + end + end + # comparison for non mutating functions + function compare(l, f, args...) + lc = copy(l) + v = collect(l) + lres = compare!(v, l, f, args...) + @test lc == l + @test lres !== l + end + + for _ in 1:10 + n = rand(0:10) + v = rand(Int, n) + l = MutableLinkedList{Int}(v...) + i, j = rand(0:n+10, 2) + v1, v2 = sort(rand(0:n, 2)) + + # first compare the non mutating functions + compare(l, getindex, i) + compare(l, getindex, i:j) + compare(l, last) + compare(l, first) + compare(v, l, reverse) + compare(v, l, isempty) + compare(v, l, length) + compare(v, l, collect) + compare(v, l, map) + compare(v, l, (x, y) -> filter(y, x), iseven) + compare(v, l, (x, y) -> filter(y, x), false) + compare(v, l, (x, y) -> filter(y, x), true) + compare(v, l, copy) + + compare!(v, l, splice!, i) + compare!(v, l, splice!, i:j) + compare!(v, l, splice!, i, v1) + compare!(v, l, splice!, i:j, v1:v2) + compare!(l, setindex!, i, v1) + compare!(v, l, pop!) + compare!(v, l, popfirst!) + compare!(v, l, popat!, i) + compare!(v, l, (x, y) -> filter!(y, x), isodd) + compare!(v, l, reverse!) + compare!(v, l, push!, i) + compare!(v, l, pushfirst!, i) + compare!(v, l, append!, rand(Int, rand(0:3))...) + compare!(v, l, prepend!, rand(Int, rand(0:3))...) + compare!(v, l, deleteat!, i) + compare!(v, l, deleteat!, i:j) + compare!(v, l, empty!) + end + end + @testset "core functionality" begin n = 10 - @testset "push back / pop back" begin l = MutableLinkedList{Int}() @@ -26,8 +320,8 @@ @test last(l) == i if i > 4 @test getindex(l, i) == i - @test getindex(l, 1:floor(Int, i/2)) == MutableLinkedList{Int}(1:floor(Int, i/2)...) - @test l[1:floor(Int, i/2)] == MutableLinkedList{Int}(1:floor(Int, i/2)...) + @test getindex(l, 1:floor(Int, i / 2)) == MutableLinkedList{Int}(1:floor(Int, i / 2)...) + @test l[1:floor(Int, i / 2)] == MutableLinkedList{Int}(1:floor(Int, i / 2)...) setindex!(l, 0, i - 2) @test l == MutableLinkedList{Int}(1:i-3..., 0, i-1:i...) setindex!(l, i - 2, i - 2) @@ -38,12 +332,6 @@ for (j, k) in enumerate(l) @test j == k end - if i > 3 - l1 = MutableLinkedList{Int32}(1:i...) - io = IOBuffer() - @test sprint(io -> show(io, iterate(l1))) == "(1, DataStructures.ListNode{Int32}(2))" - @test sprint(io -> show(io, iterate(l1, l1.node.next.next))) == "(2, DataStructures.ListNode{Int32}(3))" - end cl = collect(l) @test isa(cl, Vector{Int}) @test cl == collect(1:i) @@ -90,76 +378,43 @@ end - @testset "append / delete / copy / reverse" begin - for i = 1:n - l = MutableLinkedList{Int}(1:n...) - - @testset "append" begin - l2 = MutableLinkedList{Int}(n+1:2n...) - cl2 = collect(l2) - append!(l, l2) - @test collect(l2) == cl2 # l2 should not be mutated - @test l == MutableLinkedList{Int}(1:2n...) - @test collect(l) == collect(MutableLinkedList{Int}(1:2n...)) - l3 = MutableLinkedList{Int}(1:n...) - append!(l3, n+1:2n...) - @test l3 == MutableLinkedList{Int}(1:2n...) - @test collect(l3) == collect(MutableLinkedList{Int}(1:2n...)) - end - - @testset "delete" begin - delete!(l, n+1:2n) - @test l == MutableLinkedList{Int}(1:n...) - for i = n:-1:1 - delete!(l, i) - end - @test l == MutableLinkedList{Int}() - l = MutableLinkedList{Int}(1:n...) - @test_throws BoundsError delete!(l, n-1:2n) - @test_throws BoundsError delete!(l, 2n) - end - - @testset "copy" begin - l2 = copy(l) - @test l == l2 - end - - @testset "reverse" begin - l2 = MutableLinkedList{Int}(n:-1:1...) - @test l == reverse(l2) - end + @testset "insert" begin + l = MutableLinkedList{Int}(1:n...) + @test_throws BoundsError insert!(l, 0, 0) + @test_throws BoundsError insert!(l, n + 2, 0) + @test insert!(l, n + 1, n + 1) == MutableLinkedList{Int}(1:n+1...) + @test insert!(l, 1, 0) == MutableLinkedList{Int}(0:n+1...) + @test insert!(l, n + 2, -1) == MutableLinkedList{Int}(0:n..., -1, n + 1) + for i = n:-1:1 + insert!(l, n + 2, i) end + @test l == MutableLinkedList{Int}(0:n..., 1:n..., -1, n + 1) + @test l.len == 2n + 3 end - @testset "map / filter" begin - for i = 1:n - @testset "map" begin - l = MutableLinkedList{Int}(1:n...) - @test map(x -> 2x, l) == MutableLinkedList{Int}(2:2:2n...) - l2 = MutableLinkedList{Float64}() - @test map(x -> x*im, l2) == MutableLinkedList{Complex{Float64}}() - @test map(Int32, l2) == MutableLinkedList{Int32}() - f(x) = x % 2 == 0 ? convert(Int8, x) : convert(Float16, x) - @test typeof(map(f, l)) == MutableLinkedList{Real} - end - - @testset "filter" begin - l = MutableLinkedList{Int}(1:n...) - @test filter(x -> x % 2 == 0, l) == MutableLinkedList{Int}(2:2:n...) - end + @testset "popat" begin + l = MutableLinkedList{Int}(1:n...) + @test_throws BoundsError popat!(l, 0) + @test_throws BoundsError popat!(l, n + 1) + @test popat!(l, 0, missing) === missing + @test popat!(l, n + 1, Inf) === Inf + for i = 2:n-1 + @test popat!(l, 2) == i + end + @test l == MutableLinkedList{Int}(1, n) + @test l.len == 2 - @testset "show" begin - l = MutableLinkedList{Int32}(1:n...) - io = IOBuffer() - @test sprint(io -> show(io, l.node.next)) == "$(typeof(l.node.next))($(l.node.next.data))" - io1 = IOBuffer() - write(io1, "MutableLinkedList{Int32}("); - write(io1, join(l, ", ")); - write(io1, ")") - seekstart(io1) - @test sprint(io -> show(io, l)) == read(io1, String) - end + l2 = MutableLinkedList{Int}(1:n...) + for i = n-1:-1:2 + @test popat!(l2, l2.len - 1, 0) == i end + @test l2 == MutableLinkedList{Int}(1, n) + @test l2.len == 2 + @test popat!(l2, 1) == 1 + @test popat!(l2, 1) == n + @test l2 == MutableLinkedList{Int}() + @test l2.len == 0 + @test_throws BoundsError popat!(l2, 1) end end @@ -168,11 +423,11 @@ r = Int[] m = 100 - for k = 1 : m + for k = 1:m la = rand(1:20) x = rand(1:1000, la) - for i = 1 : la + for i = 1:la if rand(Bool) push!(r, x[i]) push!(l, x[i]) @@ -186,7 +441,7 @@ @test collect(l) == r lr = rand(1:length(r)) - for i = 1 : lr + for i = 1:lr if rand(Bool) pop!(r) pop!(l) @@ -200,4 +455,5 @@ @test collect(l) == r end end + end