diff --git a/Project.toml b/Project.toml index f546f78..0c4071d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,9 +1,9 @@ name = "OrderedCollections" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.7.0" +version = "1.8.0" [compat] -julia = "1.6" +julia = "1.7.1" [extras] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/docs/src/index.md b/docs/src/index.md index 43da3ef..78b8cd5 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -77,6 +77,7 @@ Indeed the preceeding example (with the io redirected to `devnull`), runs 4x fas OrderedDict OrderedSet LittleDict +LittleSet freeze OrderedCollections.isordered ``` diff --git a/src/OrderedCollections.jl b/src/OrderedCollections.jl index 9687f62..bd3fdb9 100644 --- a/src/OrderedCollections.jl +++ b/src/OrderedCollections.jl @@ -17,12 +17,13 @@ module OrderedCollections valtype, lastindex, nextind, copymutable, emptymutable, dict_with_eltype - export OrderedDict, OrderedSet, LittleDict + export OrderedDict, OrderedSet, LittleDict, LittleSet export freeze include("dict_support.jl") include("ordered_dict.jl") include("little_dict.jl") + include("little_set.jl") include("ordered_set.jl") include("dict_sorting.jl") diff --git a/src/dict_support.jl b/src/dict_support.jl index b44f514..21907a2 100644 --- a/src/dict_support.jl +++ b/src/dict_support.jl @@ -4,3 +4,7 @@ # so they are redefined here. _tablesz(x::Integer) = x < 16 ? 16 : one(x)<<((sizeof(x)<<3)-leading_zeros(x-1)) hashindex(key, sz) = (reinterpret(Int,(hash(key))) & (sz-1)) + 1 + +const orderedset_seed = UInt === UInt64 ? 0x2114638a942a91a5 : 0xd86bdbf1 + +struct NotFoundSentinel end # Struct to mark not not found diff --git a/src/little_dict.jl b/src/little_dict.jl index 9669cbb..098bd51 100644 --- a/src/little_dict.jl +++ b/src/little_dict.jl @@ -5,7 +5,7 @@ const StoreType{T} = Union{Tuple{Vararg{T}}, AbstractVector{T}} end """ - LittleDict(keys, vals)<:AbstractDict + LittleDict(keys, vals) <: AbstractDict An ordered dictionary type for small numbers of keys. Rather than using `hash` or some other sophisticated measure @@ -57,7 +57,7 @@ end # Other iterators should be copied to a Vector LittleDict(ks, vs) = LittleDict(collect(ks), collect(vs)) -function LittleDict{K,V}(itr) where {K,V} +function LittleDict{K, V}(itr) where {K,V} ks = K[] vs = V[] for val in itr @@ -132,7 +132,6 @@ function Base.map!(f, iter::Base.ValueIterator{<:LittleDict}) return iter end -struct NotFoundSentinel end # Struct to mark not not found function Base.get(dd::LittleDict, key, default) @assert length(dd.keys) == length(dd.vals) for ii in 1:length(dd.keys) diff --git a/src/little_set.jl b/src/little_set.jl new file mode 100644 index 0000000..9053596 --- /dev/null +++ b/src/little_set.jl @@ -0,0 +1,313 @@ + +""" + LittleSet([itr]) <: AbstractSet + +Constructs an ordered set optimized for a small number of elements, given the +iterable `itr`. The underlying data is stored as either an `AbstractVector` or +a `Tuple` and is optimal for 30-50 elements, similar to [`LittleDict`](@ref). +""" +struct LittleSet{T, D<:StoreType{T}} <: AbstractSet{T} + data::D + + LittleSet{T, D}(data) where {T,D} = new{T, D}(data) + function LittleSet{T, Tuple{Vararg{T}}}(@nospecialize(data)) where {T} + new_data = isa(data, Tuple) ? data : Tuple(data) + new{T, Tuple{Vararg{T}}}(new_data) + end + function (LittleSet{T, Tuple{Vararg{T}}} where {T})(@nospecialize(data)) + T2 = eltype(data) + new{T2, Tuple{Vararg{T2}}}(data) + end + function LittleSet{T}(data::AbstractVector) where {T} + if eltype(data) == T + return new{T, typeof(data)}(data) + else + d = convert(AbstractVector{T}, data) + return new{T, typeof(d)}(d) + end + end + LittleSet{T}(data::Tuple{Vararg{T}}) where {T} = new{T, typeof(data)}(data) + LittleSet{T}(itr) where {T} = union!(LittleSet{T}(), itr) + LittleSet{T}() where {T} = new{T, Vector{T}}(Vector{T}()) + LittleSet() = LittleSet{Any}() + function LittleSet(itr) + IET = Base.IteratorEltype(itr) + if isa(IET, Base.HasEltype) + LittleSet{eltype(itr)}(itr) + else + T = Base.@default_eltype(itr) + if (Base.isconcretetype(T) || T === Union{}) + return LittleSet{T}(itr) + else + return Base.grow_to!(LittleSet{T}(), itr) + end + end + end + + function Base.empty(s::LittleSet{T, D}) where {T, D} + if isa(s, LittleSet{T, Tuple{Vararg{T}}}) + return new{T, Tuple{Vararg{T}}}(()) + elseif D <: Tuple + return new{T, Tuple{}}(()) + else + return new{T, D}(empty(getfield(s, :data))) + end + end + function Base.emptymutable(s::LittleSet{T, D}, ::Type{U}=T) where {T, D, U} + if D <: Tuple + new_data = U[] + else + new_data = Base.emptymutable(getfield(s, :data), U) + end + return new{U, typeof(new_data)}(new_data) + end +end + +const OpaqueLittleSet{T} = LittleSet{T, Tuple{Vararg{T}}} +const UnfrozenLittleSet{T} = LittleSet{T, <:AbstractVector{T}} +const FrozenLittleSet{T} = LittleSet{T, <:Tuple} + +function Base.Tuple(s::LittleSet) + data = getfield(s, :data) + isa(data, Tuple) ? data : Tuple(data) +end + +freeze(s::AbstractSet{T}) where {T} = LittleSet{T}(Tuple(s)) + +hash(s::LittleSet, h::UInt) = hash(getfield(s, :data), hash(orderedset_seed, h)) + +function Base.length(s::LittleSet) + data = getfield(s, :data) + isa(data, Tuple) ? nfields(data) : length(data) +end + +function Base.isempty(s::LittleSet) + data = getfield(s, :data) + isa(data, Tuple) ? nfields(data) == 0 : isempty(data) +end + +function Base.copy(s::LittleSet{T, D}) where {T, D} + # since `Base.copy` is a shallow copy on collections, an immutable collection + # like `Tuple` is treated the same + if D <: Tuple + return s + else + return LittleSet{T, D}(copy(getfield(s, :data))) + end +end + +function Base.copymutable(s::LittleSet{T}) where {T} + data = getfield(s, :data) + if isa(data, Tuple) + i = nfields(data) + new_data = Vector{T}(undef, n) + while i != 0 + @inbounds new_data[i] = getfield(data, i) + i -= 1 + end + else + new_data = Base.copymutable(data) + end + LittleSet{T}(new_data) +end + +function Base.sizehint!(s::UnfrozenLittleSet, sz) + sizehint!(getfield(s, :data), sz) + return s +end +Base.iterate(s::LittleSet, state...) = iterate(getfield(s, :data), state...) + +function Base.in(x, s::LittleSet) + data = getfield(s, :data) + if isa(data, Tuple) + n = nfields(data) + while n > 0 + isequal(x, getfield(data, n)) && return true + n -= 1 + end + return false + else + return in(x, data) + end +end + +# HACK: this is a temporary hack to get around the lack of `sort` available for tuples +function Base.sort(s::FrozenLittleSet{T}; ks...) where {T} + LittleSet{T}(sort(T[getfield(s, :data)...]; ks...)) +end +function Base.sort(s::UnfrozenLittleSet{T}; ks...) where {T} + LittleSet{T}(sort(getfield(s, :data); ks...)) +end + +function Base.sort!(s::UnfrozenLittleSet; ks...) + sort!(getfield(s, :data); ks...) + return s +end + +Base.first(s::UnfrozenLittleSet, n::Integer) = LittleSet(first(getfield(s, :data), n)) +function Base.first(s::FrozenLittleSet{T}, n::Integer) where {T} + N = Int(n) + N < 0 && throw(ArgumentError("Number of elements must be nonnegative")) + data = getfield(s, :data) + stop = nfields(data) + if stop <= N + # max number of elements is everything so it's equivalent to `copy` + return s + elseif isa(s, LittleSet{T, Tuple{Vararg{T}}}) + return LittleSet{T, Tuple{Vararg{T}}}(Tuple(@inbounds(collect(data)[1:N]))) + else + return LittleSet{T}(ntuple(i->getfield(data, i), min(nfields(data), n))) + end +end + +Base.last(s::UnfrozenLittleSet, n::Integer) = LittleSet(last(getfield(s, :data), n)) +function Base.last(s::FrozenLittleSet{T}, n::Integer) where {T} + N = Int(n) + N < 0 && throw(ArgumentError("Number of elements must be nonnegative")) + data = getfield(s, :data) + stop = nfields(data) + offset = stop - N + if offset < 1 + # offset less than one means each element is returned so it's equivalent to `copy` + return s + elseif isa(s, LittleSet{T, Tuple{Vararg{T}}}) + return LittleSet{T, Tuple{Vararg{T}}}(Tuple(@inbounds(collect(data)[1:N]))) + else + return LittleSet{T}(ntuple(i->getfield(data, offset + i), N)) + end +end + +function Base.union(x::FrozenLittleSet{T1}, y::FrozenLittleSet{T2}) where {T1, T2} + xdata = getfield(x, :data) + nx = nfields(xdata) + ydata = getfield(y, :data) + ny = nfields(ydata) + if isa(x, LittleSet{T1, Tuple{Vararg{T1}}}) || isa(y, LittleSet{T2, Tuple{Vararg{T2}}}) + if nx < ny # nx is smaller so search for x items in y + newdata = (filter(!in(y), x)..., ydata...) + else # ny is smaller so search for y items in x + newdata = (xdata..., getfield(filter(!in(x), y), :data)...) + end + T = Union{T1, T2} + return LittleSet{T, Tuple{Vararg{T}}}(newdata) + else + if nx < ny # nx is smaller so search for x items in y + return LittleSet((filter(!in(ydata), xdata)..., ydata...)) + else # ny is smaller so search for y items in x + return LittleSet((xdata..., filter(!in(xdata), ydata)...)) + end + end +end + +function Base.push!(s::UnfrozenLittleSet, val) + data = getfield(s, :data) + if !in(val, data) + push!(data, val) + end + return s +end + +Base.pop!(s::UnfrozenLittleSet) = pop!(getfield(s, :data)) +function Base.pop!(s::UnfrozenLittleSet, key) + data = getfield(s, :data) + for i in eachindex(data) + k = @inbounds(data[i]) + if (key === k || isequal(key, k)) + deleteat!(data, i) + return k + end + end + throw(KeyError(key)) +end +function Base.pop!(s::UnfrozenLittleSet, key, default) + data = getfield(s, :data) + for i in eachindex(data) + k = @inbounds(data[i]) + if (key === k || isequal(key, k)) + deleteat!(data, i) + return k + end + end + return default +end + +Base.empty!(s::UnfrozenLittleSet) = (empty!(getfield(s, :data)); s) + +function Base.delete!(s::UnfrozenLittleSet, key) + data = getfield(s, :data) + for i in eachindex(data) + k = @inbounds(data[i]) + if (key === k || isequal(key, k)) + deleteat!(data, i) + break + end + end + return s +end + +function Base.filter(f, s::LittleSet{T}) where {T} + newdata = filter(f, getfield(s, :data)) + if isa(s, OpaqueLittleSet) + return OpaqueLittleSet{T}(newdata) + else + return LittleSet(newdata) + end +end +function Base.filter!(f, s::UnfrozenLittleSet) + filter!(f, getfield(s, :data)) + return s +end + +# these are copied from Julia's "base/set.jl" because tuple replace isn't +# supported before Julia v1.7 +function check_count(count::Integer) + count < 0 && throw(DomainError(count, "`count` must not be negative (got $count)")) + return min(count, typemax(Int)) % Int +end + +function Base.replace( + s::LittleSet{T}, + old_new::Pair{F, S}...; + count::Integer=typemax(Int) +) where {T, F, S} + replace(s; count=count) do x + for o_n in old_new + isequal(first(o_n), x) && return last(o_n) + end + return x + end +end +function Base.replace( + f::Union{Function, Type}, + s::LittleSet{T, D}; + count::Integer=typemax(Int) +) where {T, D} + old_data = getfield(s, :data) + if isa(old_data, Tuple) + c = check_count(count) + n = nfields(old_data) + v = Vector{Any}(undef, n) + for i in Base.OneTo(n) + old_item = @inbounds(getfield(old_data, i)) + if c > 0 + new_item = f(old_item) + if new_item == old_item + c -= 1 + end + else + new_item = old_item + end + @inbounds(setindex!(v, new_item, i)) + end + new_data = (v...,) + T2 = eltype(new_data) + if isa(s, LittleSet{T, Tuple{Vararg{T}}}) + return LittleSet{T2, Tuple{Vararg{T2}}}(new_data) + else + return LittleSet{T2, typeof(new_data)}(new_data) + end + else + new_data = replace(f, old_data, count=count) + return LittleSet(new_data) + end +end diff --git a/src/ordered_set.jl b/src/ordered_set.jl index 42fe43d..34609e9 100644 --- a/src/ordered_set.jl +++ b/src/ordered_set.jl @@ -84,7 +84,6 @@ function filter!(f::Function, s::OrderedSet) return s end -const orderedset_seed = UInt === UInt64 ? 0x2114638a942a91a5 : 0xd86bdbf1 function hash(s::OrderedSet, h::UInt) h = hash(orderedset_seed, h) s.dict.ndel > 0 && rehash!(s.dict) diff --git a/test/runtests.jl b/test/runtests.jl index 07a9879..27ad299 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,6 +6,7 @@ using Random, Serialization tests = [ "little_dict", + "little_set", "ordered_dict", "ordered_set", ] diff --git a/test/test_little_set.jl b/test/test_little_set.jl new file mode 100644 index 0000000..b3abe59 --- /dev/null +++ b/test/test_little_set.jl @@ -0,0 +1,300 @@ +using OrderedCollections, Test + +@testset "LittleSet" begin + + @testset "Constructors" begin + @test isa(LittleSet{Int}(keys(LittleDict{Int,Float64}(Vector{Int}(), Vector{Float64}()))), LittleSet{Int}) + @test isa(LittleSet{Int}(keys(LittleDict([(1,2.0)]))), LittleSet{Int}) + @test isa(LittleSet(), LittleSet{Any}) + @test isa(LittleSet([1,2,3]), LittleSet{Int}) + @test isa(LittleSet{Int}([3]), LittleSet{Int}) + data_in = (1, "banana", ()) + s = LittleSet(data_in) + data_out = collect(s) + @test isa(data_out, Array{Any,1}) + @test tuple(data_out...) === data_in + @test tuple(data_in...) === tuple(s...) + @test length(data_out) == length(data_in) + end + + @testset "hash" begin + s1 = LittleSet{String}(["bar", "foo"]) + s2 = LittleSet{String}(["foo", "bar"]) + s3 = LittleSet{String}(["baz"]) + @test hash(s1) != hash(s2) + @test hash(s1) != hash(s3) + end + + @testset "isequal" begin + @test isequal(LittleSet(), LittleSet()) + @test !isequal(LittleSet(), LittleSet([1])) + @test isequal(LittleSet{Any}(Any[1,2]), LittleSet{Int}([1,2])) + @test !isequal(LittleSet{Any}(Any[1,2]), LittleSet{Int}([1,2,3])) + + @test isequal(LittleSet{Int}(), LittleSet{AbstractString}()) + @test !isequal(LittleSet{Int}(), LittleSet{AbstractString}([""])) + @test !isequal(LittleSet{AbstractString}(), LittleSet{Int}([0])) + @test !isequal(LittleSet{Int}([1]), LittleSet{AbstractString}()) + @test isequal(LittleSet{Any}([1,2,3]), LittleSet{Int}([1,2,3])) + @test isequal(LittleSet{Int}([1,2,3]), LittleSet{Any}([1,2,3])) + @test !isequal(LittleSet{Any}([1,2,3]), LittleSet{Int}([1,2,3,4])) + @test !isequal(LittleSet{Int}([1,2,3]), LittleSet{Any}([1,2,3,4])) + @test !isequal(LittleSet{Any}([1,2,3,4]), LittleSet{Int}([1,2,3])) + @test !isequal(LittleSet{Int}([1,2,3,4]), LittleSet{Any}([1,2,3])) + end + + @testset "eltype, empty" begin + s1 = empty(LittleSet([1,"hello"])) + @test isequal(s1, LittleSet()) + @test eltype(s1) === Any + s2 = empty(LittleSet{Float32}([2.0f0,3.0f0,4.0f0])) + @test isequal(s2, LittleSet()) + @test eltype(s2) === Float32 + end + + @testset "Core Functionality" begin + s = LittleSet(); push!(s,1); push!(s,2); push!(s,3) + @test !isempty(s) + @test in(1,s) + @test in(2,s) + @test length(s) == 3 + push!(s,1); push!(s,2); push!(s,3) + @test length(s) == 3 + @test pop!(s,1) == 1 + @test !in(1,s) + @test in(2,s) + @test length(s) == 2 + @test_throws KeyError pop!(s,1) + @test pop!(s,1,:foo) == :foo + @test length(delete!(s,2)) == 1 + @test !in(1,s) + @test !in(2,s) + @test pop!(s) == 3 + @test length(s) == 0 + @test isempty(s) + end + + @testset "copy" begin + data_in = [1,2,9,8,4] + s = LittleSet(data_in) + c = copy(s) + @test isequal(s,c) + v = pop!(s) + @test !in(v,s) + @test in(v,c) + push!(s,100) + push!(c,200) + @test !in(100,c) + @test !in(200,s) + end + + @testset "sizehint!, empty" begin + s = LittleSet([1]) + @test isequal(sizehint!(s, 10), LittleSet([1])) + @test isequal(empty!(s), LittleSet()) + # TODO: rehash + end + + @testset "iterate" begin + for data_in in ((7,8,4,5), + ("hello", 23, 2.7, (), [], (1,8))) + s = LittleSet(data_in) + + s_new = LittleSet() + for el in s + push!(s_new, el) + end + @test isequal(s, s_new) + + t = tuple(s...) + + @test t === data_in + @test length(t) == length(s) + for (e,f) in zip(t,s) + @test e === f + end + end + end + + @testset "union" begin + @test isequal(union(LittleSet([1])),LittleSet([1])) + + s = ∪(LittleSet([1,2]), LittleSet([3,4])) + @test isequal(s, LittleSet([1,2,3,4])) + + s = ∪(LittleSet((1, 2)), LittleSet((3,4))) + @test isequal(s, LittleSet((1,2,3,4))) + + s1 = LittleSet{Int, Tuple{Vararg{Int}}}((1, 2)) + s2 = LittleSet{Int, Tuple{Vararg{Int}}}((3,4)) + s = @inferred(∪(s1, s2)) + @test isequal(s, LittleSet((1,2,3,4))) + + s = union(LittleSet([5,6,7,8]), LittleSet([7,8,9])) + @test isequal(s, LittleSet([5,6,7,8,9])) + s = LittleSet([1,3,5,7]) + union!(s,(2,3,4,5)) + # TODO: order is not the same, so isequal should return false... + @test isequal(s, LittleSet([1,2,3,4,5,7])) + end + + @testset "first" begin + @test_throws ArgumentError first(LittleSet()) + @test first(LittleSet([2])) == 2 + + s = LittleSet([1,2,3]) + @test length(first(s, 2)) == 2 + @test length(first(s, 10)) == 3 + + s = LittleSet((1,2,3)) + @test length(first(s, 2)) == 2 + @test length(first(s, 10)) == 3 + + s = LittleSet{Int, Tuple{Vararg{Int}}}((1,2,3)) + @test length(@inferred(first(s, 2))) == 2 + end + + @testset "last" begin + s = LittleSet([1,2,3]) + @test length(last(s, 2)) == 2 + @test length(last(s, 10)) == 3 + + s = LittleSet((1,2,3)) + @test length(last(s, 2)) == 2 + @test length(last(s, 10)) == 3 + + s = LittleSet{Int, Tuple{Vararg{Int}}}((1,2,3)) + @test length(@inferred(last(s, 2))) == 2 + end + + @testset "intersect" begin + @test isequal(intersect(LittleSet([1])),LittleSet([1])) + s = ∩(LittleSet([1,2]), LittleSet([3,4])) + @test isequal(s, LittleSet()) + s = intersect(LittleSet([5,6,7,8]), LittleSet([7,8,9])) + @test isequal(s, LittleSet([7,8])) + @test isequal(intersect(LittleSet([2,3,1]), LittleSet([4,2,3]), LittleSet([5,4,3,2])), LittleSet([2,3])) + end + + @testset "setdiff" begin + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet()), LittleSet([1,2,3])) + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet([1])), LittleSet([2,3])) + @test isequal(setdiff(LittleSet([1,2,3]), Set([1])), LittleSet([2,3])) + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet([1,2])), LittleSet([3])) + @test isequal(setdiff(LittleSet([1,2,3]), Set([1,2])), LittleSet([3])) + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet([1,2,3])), LittleSet()) + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet([4])), LittleSet([1,2,3])) + @test isequal(setdiff(LittleSet([1,2,3]), LittleSet([4,1])), LittleSet([2,3])) + s = LittleSet([1,3,5,7]) + setdiff!(s,(3,5)) + @test isequal(s,LittleSet([1,7])) + s = LittleSet([1,2,3,4]) + setdiff!(s, LittleSet([2,4,5,6])) + @test isequal(s,LittleSet([1,3])) + end + + @testset "ordering" begin + @test LittleSet() < LittleSet([1]) + @test LittleSet([1]) < LittleSet([1,2]) + @test !(LittleSet([3]) < LittleSet([1,2])) + @test !(LittleSet([3]) > LittleSet([1,2])) + @test LittleSet([1,2,3]) > LittleSet([1,2]) + @test !(LittleSet([3]) <= LittleSet([1,2])) + @test !(LittleSet([3]) >= LittleSet([1,2])) + @test LittleSet([1]) <= LittleSet([1,2]) + @test LittleSet([1,2]) <= LittleSet([1,2]) + @test LittleSet([1,2]) >= LittleSet([1,2]) + @test LittleSet([1,2,3]) >= LittleSet([1,2]) + @test !(LittleSet([1,2,3]) >= LittleSet([1,2,4])) + @test !(LittleSet([1,2,3]) <= LittleSet([1,2,4])) + end + + @testset "issubset, symdiff" begin + for (l,r) in ((LittleSet([1,2]), LittleSet([3,4])), + (LittleSet([5,6,7,8]), LittleSet([7,8,9])), + (LittleSet([1,2]), LittleSet([3,4])), + (LittleSet([5,6,7,8]), LittleSet([7,8,9])), + (LittleSet([1,2,3]), LittleSet()), + (LittleSet([1,2,3]), LittleSet([1])), + (LittleSet([1,2,3]), LittleSet([1,2])), + (LittleSet([1,2,3]), LittleSet([1,2,3])), + (LittleSet([1,2,3]), LittleSet([4])), + (LittleSet([1,2,3]), LittleSet([4,1]))) + @test issubset(intersect(l,r), l) + @test issubset(intersect(l,r), r) + @test issubset(l, union(l,r)) + @test issubset(r, union(l,r)) + @test isequal(union(intersect(l,r),symdiff(l,r)), union(l,r)) + end + @test ⊆(LittleSet([1]), LittleSet([1,2])) + + @test ⊊(LittleSet([1]), LittleSet([1,2])) + @test !⊊(LittleSet([1]), LittleSet([1])) + @test ⊈(LittleSet([1]), LittleSet([2])) + + @test symdiff(LittleSet([1,2,3,4]), LittleSet([2,4,5,6])) == LittleSet([1,3,5,6]) + + @test isequal(symdiff(LittleSet([1,2,3,4]), LittleSet([2,4,5,6])), LittleSet([1,3,5,6])) + + end + + @testset "filter" begin + s = LittleSet([1,2,3,4]) + @test isequal(filter(isodd,s), LittleSet([1,3])) + filter!(isodd, s) + @test isequal(s, LittleSet([1,3])) + s = LittleSet((1,2,3,4)) + @test isa(filter(isodd, s), OrderedCollections.FrozenLittleSet) + end + + @testset "replace" begin + s = LittleSet([1,2,3,4]) + @test isequal(replace(s, 1 => 0, 2 => 5), LittleSet([0, 5, 3, 4])) + s = LittleSet{Int, Tuple{Vararg{Int}}}((1,2,3,4)) + @test isequal(replace(s, 1 => 0, 2 => 5), LittleSet((0, 5, 3, 4))) + end + + @testset "empty set" begin + d = LittleSet{Char}() + @test length(d) == 0 + @test isempty(d) + @test !('c' in d) + push!(d, 'c') + @test !isempty(d) + empty!(d) + @test isempty(d) + end + + @testset "access, modification" begin + d = LittleSet{Char}() + + for c in 'a':'z' + push!(d, c) + end + + for c in 'a':'z' + @test c in d + end + + @test collect(d) == collect('a':'z') + end + + @testset "sort(!)" begin + x = [-4, 1, -5, 10, 7] + ox = LittleSet(x) + @test !issorted(ox) + sox = sort(ox) + @test issorted(sox) + sox = sort(ox; rev=true) + @test !issorted(sox) + @test issorted(sox; rev=true) + ox = LittleSet(x) + @test ox === sort!(ox) + @test issorted(ox) + ox = LittleSet(x) + @test ox === sort!(ox; rev=true) + @test !issorted(ox) + @test issorted(ox; rev=true) + end + +end # @testset LittleSet