diff --git a/src/BangBang.jl b/src/BangBang.jl index 4c4b20e..723b40a 100644 --- a/src/BangBang.jl +++ b/src/BangBang.jl @@ -14,6 +14,7 @@ export @!, deleteat!!, empty!!, finish!, + intersect!!, lmul!!, materialize!!, merge!!, @@ -31,6 +32,7 @@ export @!, setproperty!!, singletonof, splice!!, + symdiff!!, union!!, unique!! @@ -58,6 +60,8 @@ include("utils.jl") function implements end function push!! end function unique!! end +function union!! end +function symdiff!! end include("NoBang/NoBang.jl") using .NoBang: Empty, SingletonVector, singletonof diff --git a/src/NoBang/NoBang.jl b/src/NoBang/NoBang.jl index 0914890..47948c3 100644 --- a/src/NoBang/NoBang.jl +++ b/src/NoBang/NoBang.jl @@ -7,7 +7,7 @@ using Base: ImmutableDict using Requires using ConstructionBase: constructorof, setproperties -using ..BangBang: push!!, unique!!, implements +using ..BangBang: push!!, unique!!, union!!, symdiff!!, implements include("singletoncontainers.jl") include("base.jl") diff --git a/src/NoBang/base.jl b/src/NoBang/base.jl index fbcb5e0..7b911de 100644 --- a/src/NoBang/base.jl +++ b/src/NoBang/base.jl @@ -151,6 +151,10 @@ setproperty(value, name, x) = setproperties(value, NamedTuple{(name,)}((x,))) materialize(::Any, x) = Broadcast.materialize(x) -@inline _union(a, b) = union(a, b) +@inline _union(args...) = union(args...) -@inline _setdiff(a, b) = setdiff(a, b) +@inline _intersect(args...) = intersect(args...) + +@inline _setdiff(args...) = setdiff(args...) + +@inline _symdiff(args...) = symdiff(args...) diff --git a/src/NoBang/emptycontainers.jl b/src/NoBang/emptycontainers.jl index c165457..ed7ee87 100644 --- a/src/NoBang/emptycontainers.jl +++ b/src/NoBang/emptycontainers.jl @@ -75,10 +75,15 @@ append(e::Empty, ::Empty) = e _empty(x::Empty) = x resize(::Empty{T}, n::Integer) where {T <: AbstractVector} = similar(T, (n,)) -_union(::Empty{T}, x) where {T} = unique!!(T(x)) -_union(e::Empty, ::Empty) = e +_union(::Empty{T}, x, ys...) where {T} = union!!(unique!!(T(x)), ys...) +_union(e::Empty, ::Empty...) = e -_setdiff(x::Empty, _) = x +_intersect(x::Empty, ys...) = x + +_setdiff(x::Empty, ys...) = x + +_symdiff(::Empty{T}, x, ys...) where {T} = symdiff!!(unique!!(T(x)), ys...) +_symdiff(e::Empty, ::Empty...) = e _setindex(::Empty{T}, v, k) where {T <: AbstractDict} = T(SingletonDict(k, v)) Base.get(::Empty, _, default) = default diff --git a/src/base.jl b/src/base.jl index bace124..be631e5 100644 --- a/src/base.jl +++ b/src/base.jl @@ -710,15 +710,50 @@ false ``` """ union!! -union!!(set, itr) = may(union!, set, itr) -union!!(set, itr, itrs...) = foldl(union!!, itrs, init = union!!(set, itr)) +union!!(args...) = may(union!, args...) pure(::typeof(union!)) = NoBang._union _asbb(::typeof(union!)) = union!! -possible(::typeof(union!), ::C, ::I) where {C<:Union{AbstractSet,AbstractVector},I} = +possible(::typeof(union!), ::C, ts...) where C<:Union{AbstractSet,AbstractVector} = implements(push!, C) && - IteratorEltype(I) isa HasEltype && promote_type(eltype(C), eltype(I)) <: eltype(C) -possible(::typeof(union!), ::Empty, ::Any) = false + all(t -> IteratorEltype(t) isa HasEltype, ts) && + promote_type(eltype(C), map(eltype, ts)...) <: eltype(C) +possible(::typeof(union!), ::Empty, ts...) = false +# TODO: simplify `possible` after deciding what to do with dictionaries + +""" + intersect!!(setlike, others...) -> setlike′ + +Return the set of elements in `setlike` and in all the collections +`others`. Mutate `setlike` if possible. + +This function "owns" `setlike` but not `others`; i.e., returned value +`setlike′` does not alias any of `others`. For example, +`other = Set([1]); intersect!!(Set([1, 2]), other)` does not return `other` as-is. + +# Examples +```jldoctest +julia> using BangBang + +julia> xs = Set([1, 2]); + +julia> ys = intersect!!(xs, [1]); # mutates `xs` as it's possible + +julia> ys == Set([1]) +true + +julia> ys === xs # `xs` is returned +true +``` +""" +intersect!! +intersect!!(args...) = may(intersect!, args...) + +pure(::typeof(intersect!)) = NoBang._intersect +_asbb(::typeof(intersect!)) = intersect!! +possible(::typeof(intersect!), ::C, ::Any...) where {C<:Union{AbstractSet,AbstractVector}} = + implements(push!, C) +possible(::typeof(intersect!), ::Empty, ts...) = false # TODO: simplify `possible` after deciding what to do with dictionaries """ @@ -729,8 +764,7 @@ Return the set of elements in `setlike` but not in any of the collections This function "owns" `setlike` but not `others`; i.e., returned value `setlike′` does not alias any of `others`. For example, -`setdiff!!(Empty(Set), other)` shallow-copies `other` instead of -returning `other` as-is. +`other = Set([]); setdiff!!(Empty(Set), other)` does not return `other` as-is. # Examples ```jldoctest @@ -748,12 +782,49 @@ true ``` """ setdiff!! -setdiff!!(set, itr) = may(setdiff!, set, itr) -setdiff!!(set, itr, itrs...) = foldl(setdiff!!, itrs, init = setdiff!!(set, itr)) +setdiff!!(args...) = may(setdiff!, args...) pure(::typeof(setdiff!)) = NoBang._setdiff _asbb(::typeof(setdiff!)) = setdiff!! -possible(::typeof(setdiff!), ::C, ::Any) where {C<:Union{AbstractSet,AbstractVector}} = +possible(::typeof(setdiff!), ::C, ::Any...) where {C<:Union{AbstractSet,AbstractVector}} = implements(push!, C) -possible(::typeof(setdiff!), ::Empty, ::Any) = false +possible(::typeof(setdiff!), ::Empty, ts...) = false +# TODO: simplify `possible` after deciding what to do with dictionaries + +""" + symdiff!!(setlike, others...) -> setlike′ + +Return the set of elements that occur an odd number of times in +`setlike` and the `others` collections. Mutate `setlike` if possible. + +This function "owns" `setlike` but not `others`; i.e., returned value +`setlike′` does not alias any of `others`. For example, +`symdiff!!(Empty(Set), other)` shallow-copies `other` instead of +returning `other` as-is. + +# Examples +```jldoctest +julia> using BangBang + +julia> xs = Set([1, 2]); + +julia> ys = symdiff!!(xs, [2, 3]); # mutates `xs` as it's possible + +julia> ys == Set([1, 3]) +true + +julia> ys === xs # `xs` is returned +true +``` +""" +symdiff!! +symdiff!!(args...) = may(symdiff!, args...) + +pure(::typeof(symdiff!)) = NoBang._symdiff +_asbb(::typeof(symdiff!)) = symdiff!! +possible(::typeof(symdiff!), ::C, ts...) where C<:Union{AbstractSet,AbstractVector} = + implements(push!, C) && + all(t -> IteratorEltype(t) isa HasEltype, ts) && + promote_type(eltype(C), map(eltype, ts)...) <: eltype(C) +possible(::typeof(symdiff!), ::Empty, ts...) = false # TODO: simplify `possible` after deciding what to do with dictionaries diff --git a/src/initials.jl b/src/initials.jl index 5b658f2..5d5425d 100644 --- a/src/initials.jl +++ b/src/initials.jl @@ -84,3 +84,15 @@ InitialValues.hasinitialvalue(::Type{typeof(union!!)}) = true copyunionable(src::AbstractVector) = Base.copymutable(src) copyunionable(src) = Set(src) + +const InitIntersect!! = InitialValues.GenericInitialValue{typeof(intersect!!)} +intersect!!(dest::InitIntersect!!, src) = copyunionable(src) +intersect!!(dest, ::InitIntersect!!) = dest +intersect!!(dest::InitIntersect!!, src::InitIntersect!!) = dest +InitialValues.hasinitialvalue(::Type{typeof(intersect!!)}) = true + +const InitSymdiff!! = InitialValues.GenericInitialValue{typeof(symdiff!!)} +symdiff!!(dest::InitSymdiff!!, src) = copyunionable(src) +symdiff!!(dest, ::InitSymdiff!!) = dest +symdiff!!(dest::InitSymdiff!!, src::InitSymdiff!!) = dest +InitialValues.hasinitialvalue(::Type{typeof(symdiff!!)}) = true diff --git a/test/test_intersect.jl b/test/test_intersect.jl new file mode 100644 index 0000000..56cf8c5 --- /dev/null +++ b/test/test_intersect.jl @@ -0,0 +1,25 @@ +module TestIntersect + +include("preamble.jl") +# using MicroCollections: SingletonSet + +@testset begin + @test_returns_first intersect!!([0]) == [0] + @test_returns_first intersect!!(Set([0])) == Set([0]) + @test_returns_first intersect!!([0], [1]) == [] + @test_returns_first intersect!!([0], (1,)) == [] + @test_returns_first intersect!!([0, 1, 2], (0, 1), [1, 2]) == [1] + @test_returns_first intersect!!(Set([0]), [0]) == Set([0]) + @test_returns_first intersect!!(Set([0]), (0,)) == Set([0]) + @test_returns_first intersect!!(Set([0, 1, 2]), Set([0,1]), [1, 2]) == Set([1]) + @test intersect!!(Empty(Set)) === Empty(Set) + @test intersect!!(Empty(Vector), Empty(Set)) === Empty(Vector) + @test intersect!!(Empty(Vector), [0]) === Empty(Vector) + @test intersect!!(Empty(Set{Int}), [0]) === Empty(Set{Int}) + #= + @test intersect!!(SingletonSet((0,)), [0]) ==ₜ Set([0]) + @test intersect!!(SingletonSet((0,)), [1]) ==ₜ Set{Int}() + =# +end + +end # module diff --git a/test/test_setdiff.jl b/test/test_setdiff.jl index bdafd15..736abc2 100644 --- a/test/test_setdiff.jl +++ b/test/test_setdiff.jl @@ -4,13 +4,16 @@ include("preamble.jl") # using MicroCollections: SingletonSet @testset begin + @test_returns_first setdiff!!([0]) == [0] + @test_returns_first setdiff!!(Set([0])) == Set([0]) @test_returns_first setdiff!!([0], [0]) == [] @test_returns_first setdiff!!([0], (0,)) == [] @test_returns_first setdiff!!([0, 1, 2], (0,), [1]) == [2] @test_returns_first setdiff!!(Set([0]), [0]) == Set() @test_returns_first setdiff!!(Set([0]), (0,)) == Set() @test_returns_first setdiff!!(Set([0, 1, 2]), Set([0]), [1]) == Set([2]) - @test setdiff!!(Empty(Vector), []) === Empty(Vector) + @test setdiff!!(Empty(Set)) === Empty(Set) + @test setdiff!!(Empty(Vector), Empty(Set)) === Empty(Vector) @test setdiff!!(Empty(Set{Int}), []) === Empty(Set{Int}) #= @test setdiff!!(SingletonSet((0,)), [0]) ==ₜ Set{Int}() diff --git a/test/test_symdiff.jl b/test/test_symdiff.jl new file mode 100644 index 0000000..51c79a4 --- /dev/null +++ b/test/test_symdiff.jl @@ -0,0 +1,24 @@ +module TestSymdiff + +include("preamble.jl") +# using MicroCollections: SingletonSet + +@testset begin + @test_returns_first symdiff!!([0]) == [0] + @test_returns_first symdiff!!(Set([0])) == Set([0]) + @test_returns_first symdiff!!([0], [0]) == [] + @test_returns_first symdiff!!([0], (0,)) == [] + @test_returns_first symdiff!!([0, 1, 2], (0, 1), [1, 3]) == [1, 2, 3] + @test_returns_first symdiff!!(Set([0]), [0]) == Set() + @test_returns_first symdiff!!(Set([0]), (1,)) == Set([0, 1]) + @test_returns_first symdiff!!(Set([0, 1, 2]), Set([0, 1]), [1]) == Set([1, 2]) + @test symdiff!!(Empty(Set)) === Empty(Set) + @test symdiff!!(Empty(Vector), Empty(Vector)) === Empty(Vector) + @test symdiff!!(Empty(Set{Int}), Empty(Vector)) === Empty(Set{Int}) + #= + @test symdiff!!(SingletonSet((0,)), [0]) ==ₜ Set{Int}() + @test symdiff!!(SingletonSet((0,)), [1]) ==ₜ Set([0, 1]) + =# +end + +end # module diff --git a/test/test_union.jl b/test/test_union.jl index 3dfc24f..e91d47c 100644 --- a/test/test_union.jl +++ b/test/test_union.jl @@ -5,6 +5,8 @@ using BangBang: SingletonVector using InitialValues: InitialValue, asmonoid @testset begin + @test_returns_first union!!([0]) == [0] + @test_returns_first union!!(Set([0])) == Set([0]) @test union!!([0.0], [1.0]) ==ₜ [0.0, 1.0] @test union!!([0], [0.5]) ==ₜ [0.0, 0.5] @test union!!(Set([0.0]), [1.0]) ==ₜ Set([0.0, 1.0]) @@ -18,6 +20,7 @@ end @test union!!(Empty(Set), Empty(Set)) === Empty(Set) @test union!!(Empty(Set), Empty(Vector)) === Empty(Set) end + @test union!!(Empty(Set)) === Empty(Set) end @testset "mutation" begin