From 9011dbdddca520a4a2953a37237036d5a39a2f50 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 10 Oct 2023 12:09:38 +0200 Subject: [PATCH 1/7] complement on multi-sets and sub-set iterators --- docs/Build.jl | 1 + docs/src/features/mset.md | 84 +++++++++ src/Misc/MSet.jl | 387 ++++++++++++++++++++++++++++++++------ test/Misc.jl | 1 + test/Misc/MSet.jl | 93 +++++++++ 5 files changed, 513 insertions(+), 53 deletions(-) create mode 100644 docs/src/features/mset.md create mode 100644 test/Misc/MSet.jl diff --git a/docs/Build.jl b/docs/Build.jl index 9977684fef..fd45db4e50 100644 --- a/docs/Build.jl +++ b/docs/Build.jl @@ -44,6 +44,7 @@ pages = [ "misc/conjugacy.md", ], "Extra features" => ["features/macros.md", + "features/mset.md", ], "Examples" => "examples.md", "References" => "references.md", diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md new file mode 100644 index 0000000000..90ba63106e --- /dev/null +++ b/docs/src/features/mset.md @@ -0,0 +1,84 @@ +# Multi-sets and sub-set iterators + +```@meta +CurrentModule = Hecke +DocTestSetup = quote + using Hecke +end +``` + +## Multi-sets + +### Type and constructors + +Objects of type `Mset` consists of a dictionary whose keys are the elements in +the set, and the values are their respective multiplicity. + +```@docs +MSet +``` + +We can create multi-sets from any finite iterator, dictionary or pair of lists +with the appropriate conditions. + +```@docs +multiset +``` + +### Functions + +Existing functions for any collection of objects which are currently available +for `MSet`: + +* `similar` +* `isempty` +* `length` +* `eltype` +* `push!` +* `copy` +* `==` +* `unique` + +One can also iterate over an `MSet`, and use `filter` for a given predicate on +the keys of the underlying dictionary (not on their values!). One can also test +containment. + +While `unique` will return the keys of the underlying dictionary, one can access +the values (i.e. the multiplicities of the elements in the multi-set) via the +following functions: + +```@docs +multiplicities(::MSet) +multiplicity(::MSet{T}, ::T) where T +``` + +Note that `pop!` and `delete!` for `MSet` are available but have a different behaviour. +For an element `x` in an multi-set `M <: MSet`, then `pop!(M, x)` will remove +*one* instance of `x` in `M` - in particular `multiplicity(M, x)` will drop by +$1$. Much stronger, `delete!(M, x)` will remove *all* instances of `x` in `M` and +so `multiplicity(M, x)` will be $0$. + +Finally, one can take unions (`union`, `union!`) of an `MSet` with other +finite collections of objects. Note that `union!` will require coercion of +elements. Similarly, one can compare an `MSet` with another collection with the +`setdiff/setdiff!` functions. + +## Sub-set iterators + +### Sub-multi-sets + +```@docs +subsets(::MSet{Int}) +``` + +### Sub-sets + +```@docs +subsets(::Set{Int}) +``` + +### Sub-sets of a given size + +```@docs +subsets(::Set, ::Int) +``` diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index 2385328758..f07bdcfce5 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -1,14 +1,24 @@ -export MSet, multiplicities, multiplicity, subsets +export MSet +export multiplicities +export multiplicity +export multiset +export subsets @doc raw""" + MSet{T} <: AbstractSet{T} + Type for a multi-set, i.e. a set where elements are not unique, they (can) have a multiplicity. `MSet`s can be created from any finite iterator. -# Example +# Examples ```jldoctest julia> MSet([1,1,2,3,4,4,5]) -MSet(5, 4 : 2, 2, 3, 1 : 2) - +MSet{Int64} with 7 elements: + 5 + 4 : 2 + 2 + 3 + 1 : 2 ``` `4 : 2` means the element `4` has multiplicity `2`, i.e. was included twice. """ @@ -17,25 +27,142 @@ mutable struct MSet{T} <: AbstractSet{T} MSet{T}() where {T} = new{T}(Dict{T,Int}()) MSet{T}(itr) where {T} = union!(new{T}(Dict{T,Int}()), itr) + MSet{T}(d::Dict{T, Int}) where {T} = new{T}(d) + MSet{T}(l::Vector{T}, m::Vector{Int}) where {T} = MSet{T}(Dict(zip(l, m))) end MSet() = MSet{Any}() MSet(itr) = MSet{eltype(itr)}(itr) +MSet(d::Dict{T, Int}) where {T} = MSet{T}(d) +MSet(l::Vector{T}, m::Vector{Int}) where {T} = MSet{T}(l, m) + +@doc raw""" + multiset(iter) -> MSet{eltype(iter)} + multiset(d::Dict{T, Int}) -> MSet{T} + multiset{l::Vector{T}, m::Vector{Int}} -> MSet{T} + +Given either: + - a finite iterator `iter`; + - a dictionary `d` whose values are positive integers; + - a list `l` and a list of positive integers `m` of same length as `l`; +return the asscciated multi-set `M`. + +# Examples +```jldoctest +julia> str = "A nice sentence" +"A nice sentence" + +julia> multiset(str) +MSet{Char} with 15 elements: + 'n' : 3 + 'A' + 'c' : 2 + 'i' + 'e' : 4 + 's' + 't' + ' ' : 2 + +julia> multiset(Int[x^3%8 for x = 1:50]) +MSet{Int64} with 50 elements: + 0 : 25 + 5 : 6 + 7 : 6 + 3 : 6 + 1 : 7 + +julia> d = Dict("a" => 4, "b" => 1, "c" =>9) +Dict{String, Int64} with 3 entries: + "c" => 9 + "b" => 1 + "a" => 4 + +julia> multiset(d) +MSet{String} with 14 elements: + "c" : 9 + "b" + "a" : 4 + +julia> multiset(["a", "b", "c"], [4, 1, 9]) +MSet{String} with 14 elements: + "c" : 9 + "b" + "a" : 4 +``` +""" +multiset(iter) = MSet(iter) + +function multiset(d::Dict{T, Int}) where {T} + @req minimum(collect(values(d))) > 0 "The values of d must be positive integers" + return MSet{T}(d) +end + +function multiset(l::Vector{T}, m::Vector{Int}) where {T} + @req length(m) == length(l) "Lists must have the same length" + @req minimum(m) > 0 "Multiplicity list must consist of positive integers" + return MSet{T}(l, m) +end + +@doc raw""" + multiset(T::Type) -> MSet{T} +Create an unitialized multi-set `M` with elements of type `T`. + +# Examples +```jldoctest +julia> multiset(QQFieldElem) +MSet{QQFieldElem}() + +julia> multiset() +MSet{Any}() +``` +""" +multiset() = MSet() +multiset(T::DataType) = MSet{T}() + +@doc raw""" + Base.similar(M::MSet{T}) -> MSet{T} + Base.similar(M::MSet, T::Type) -> MSet{T} + +Create an unitialized multi-set with elements of type `T`. + +# Examples +```jldoctest +julia> m = multiset([1,1,2,3,4,4,5]) +MSet{Int64} with 7 elements: + 5 + 4 : 2 + 2 + 3 + 1 : 2 + +julia> similar(m) +MSet{Int64}() + +julia> similar(m, QQFieldElem) +MSet{QQFieldElem}() +``` +""" Base.similar(::MSet{T}) where {T} = MSet{T}() Base.similar(::MSet, T::Type) = MSet{T}() -#TODO: compact printing, remove trailing , ... the works... -function Base.show(io::IO, ::MIME"text/plain", s::MSet) - print(io,"MSet") - if isempty(s) - print(io,"{",eltype(s),"}()") - return +# We try to adopt the same conventions as in Oscar, so one-line printing should +# stay in one line, and we do not give details about what is in the MSet: the +# detailled printing will take care of it +function Base.show(io::IO, s::MSet{T}) where {T} + if isempty(s) + print(io, "MSet{$(eltype(s))}()") + elseif get(io, :supercompact, false) + io = pretty(io) + print(io, "Multi-set with ", ItemQuantity(length(s), "element")) + if !(T == Any) + print(io, " of type $T") end - print(io,"(") + else + print(io, "MSet(") first = true - for (k,v) = s.dict + for (k, v) in s.dict first || print(io, ", ") first = false if v > 1 @@ -44,7 +171,61 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) print(io, "$k") end end - print(io,")") + print(io, ")") + end +end + +# We use the one-line (hopefully implemented!) printing system for the elements +# in +function Base.show(io::IO, ::MIME"text/plain", s::MSet) + print(io,"MSet{",eltype(s),"}") + if isempty(s) + print(io,"()") + else + io = pretty(io) + szh, szw = displaysize(io) + szh -= 5 + szw -= 10 + print(io, " with ", ItemQuantity(length(s), "element"), ":") + print(io, Indent()) + d = s.dict + un = collect(keys(d)) + if length(un) <= szh + rmax = maximum(ndigits(k) for k in values(d)) + offmax = szw - (rmax + 3) + lmax = min(maximum(length(sprint(show, a)) for a in un), offmax) + for k in un + pk = sprint(show, k) + lk = length(pk) + v = d[k] + println(io) + if lk > offmax + print(io, pk[1:offmax-length(" \u2026")], " \u2026") + else + print(io, pk) + end + lk = min(offmax, lk) + if v > 1 + print(io, " "^(lmax-lk+1), ": $v") + end + end + else + lmax = maximum(length(sprint(show, a)) for a in un[1:szh]) + for i in 1:sz + println(io) + k = un[i] + lk = length(sprint(show, k)) + v = d[k] + if v > 1 + print(io, "$k", " "^(lmax-lk+1), ": $v") + else + print(io, "$k") + end + end + println(io) + print(io, "\u22ee") + end + end end Base.isempty(s::MSet) = isempty(s.dict) @@ -69,6 +250,7 @@ function Base.pop!(s::MSet{T}, x, default) where {T} y = x isa T ? x : T(x) return y in s ? pop!(s, y) : (default isa T ? default : T(default)) end + Base.pop!(s::MSet) = (val = iterate(s.dict)[1][1]; pop!(s, val)) function Base.delete!(s::MSet{T}, x) where {T} @@ -79,6 +261,8 @@ end Base.copy(s::MSet) = union!(similar(s), s) +==(s1::MSet, s2::MSet) = s1.dict == s2.dict + function Base.iterate(s::MSet) I = iterate(s.dict) I === nothing && return I @@ -97,17 +281,25 @@ function Base.iterate(s::MSet, state) end Base.union(s::MSet) = copy(s) -function Base.union(s::MSet, sets::Set...) - u = MSet{join_eltype(s, sets...)}() - union!(u,s) - for t in sets - union!(u,t) - end - return u + +function Base.union(s::MSet, sets...) + T = Base.promote_eltype(s, sets...) + u = MSet{T}() + union!(u, s) + for t in sets + union!(u, t) + end + return u end -Base.union!(s::MSet, xs) = (for x=xs; push!(s,x); end; s) -Base.union!(s::MSet, xs::AbstractArray) = (for x=xs; push!(s,x); end; s) +function Base.union!(s::MSet, xs) + T = eltype(s) + @req promote_type(T, eltype(xs)) == T "Cannot coerce elements" + for x in xs + push!(s, convert(T, x)) + end + return s +end function Base.filter(pred, s::MSet) t = similar(s) @@ -119,21 +311,74 @@ function Base.filter(pred, s::MSet) return t end +function Base.filter!(pred, s::MSet) + un = unique(s) + for x in un + if !pred(x) + delete!(s, x) + end + end + return s +end @doc raw""" - multiplicities(s::MSet) + multiplicities(s::MSet{T}) -> ValueIterator{Dict{T, Int}} + +Return an iterator for the multiplicities of all the elements in `s`. -Return an iterator for the multiplicities of all the elements. +# Examples +```jldoctest +julia> m = multiset([1,1,2,3,4,4,5]) +MSet{Int64} with 7 elements: + 5 + 4 : 2 + 2 + 3 + 1 : 2 + +julia> mult_m = multiplicities(m) +ValueIterator for a Dict{Int64, Int64} with 5 entries. Values: + 1 + 2 + 1 + 1 + 2 + +julia> collect(mult_m) +5-element Vector{Int64}: + 1 + 2 + 1 + 1 + 2 +``` """ function multiplicities(s::MSet) return values(s.dict) end @doc raw""" - multiplicity(s::MSet, x) + multiplicity(s::MSet{T}, x::T) -> Int + +Return the multiplicity of the element `x` in the multi-set `s`. If `x` is not +in `s`, return 0. -The multiplicity of the element `x`` in the multi-set `s - or zero if - `x` is not in `s`, +# Examples +```jldoctest +julia> m = multiset([1,1,2,3,4,4,5]) +MSet{Int64} with 7 elements: + 5 + 4 : 2 + 2 + 3 + 1 : 2 + +julia> multiplicity(m, 2) +1 + +julia> multiplicity(m, 6) +0 +``` """ function multiplicity(s::MSet{T}, x::T) where {T} y = x isa T ? x : T(x) @@ -148,12 +393,18 @@ function Base.unique(s::MSet) return collect(keys(s.dict)) end -function Base.setdiff(s::MSet, itrs...) - s = copy(s) - for i = itrs - if i in s - pop!(s, i) - end +Base.setdiff(s::MSet, itrs...) = setdiff!(copy(s), itrs...) + +function Base.setdiff!(s::MSet, itrs...) + for x in itr + setdiff!(s, itr) + end + return s +end + +function Base.setdiff!(s::MSet, itr) + for x in itr + pop!(s, x) end return s end @@ -169,23 +420,23 @@ struct MSubSetItr{T} end @doc raw""" - subsets(s::MSet) + subsets(s::MSet) -> MSubSetIt{T} -An iterator for all sub-multi-sets of `s`. +Return an iterator on all sub-multi-sets of `s`. """ function subsets(s::MSet{T}) where T # subsets are represented by integers in a weird base # the distinct elements are b0...bn with mult mi # subset (bi, ni) -> sum ni gi where gi = prod (mj+1) - b = collect(unique(s)) - m = [s.dict[x] for x = b] + b = unique(s) + m = Int[multiplicity(s, x) for x in b] #= not needed for the iterator g = [1] for i=2:length(b) push!(g, g[end]*(m[i]+1)) end =# - return MSubSetItr{T}(b, m, length(m) == 0 ? 1 : prod(x+1 for x=m)) + return MSubSetItr{T}(b, m, length(m) == 0 ? 1 : prod(x+1 for x in m)) end function int_to_elt(M::MSubSetItr{T}, i::Int) where T @@ -218,8 +469,18 @@ Base.IteratorSize(::Type{MSubSetItr}) = Base.HasLength() Base.IteratorEltype(::Type{MSubSetItr}) = Base.HasEltype() Base.eltype(::Type{MSubSetItr{T}}) where {T} = MSet{T} -function Base.show(io::IO, M::MSubSetItr) - println(io, "subset iterator of length $(M.length) for $(M.b) with multiplicities $(M.m)") +function Base.show(io::IO, M::MSubSetItr{T}) where {T} + print(io, "Iterator for subsets of a multi-set") + if !get(io, :supercompact, false) && T != Any + print(io, " with elements of type $T") + end +end + +function Base.show(io::IO, ::MIME"text/plain", M::MSubSetItr) + io = pretty(io) + println(io, "Iterator for subsets") + println(io, Indent(), "of ", MSet(M.b, M.m)) + print(io, Dedent(), "of length ", M.length) end #... to be completed from base/Set.jl ... @@ -231,15 +492,13 @@ struct SubSetItr{T} end @doc raw""" - subsets(s::Set) - subsets(s::Set, k::Int) + subsets(s::Set) -> SubSetItr{T} -An iterator for all sub-sets of `s`. In the second for only -subsets of size `k` are listed. +Return an iterator for all sub-sets of `s`. """ function subsets(s::Set{T}) where T # subsets are represented by integers in base 2 - b = collect(unique(s)) + b = unique(s) return SubSetItr{T}(b, 2^length(b)) end @@ -274,8 +533,18 @@ Base.IteratorSize(::Type{SubSetItr}) = Base.HasLength() Base.IteratorEltype(::Type{SubSetItr}) = Base.HasEltype() Base.eltype(::Type{SubSetItr{T}}) where {T} = Set{T} -function Base.show(io::IO, M::SubSetItr) - println(io, "subset iterator of length $(M.length) for $(M.b)") +function Base.show(io::IO, M::SubSetItr{T}) where {T} + print(io, "Iterator for subsets of a set") + if !get(io, :supercompact, false) && T != Any + print(io, " with elements of type $T") + end +end + +function Base.show(io::IO, ::MIME"text/plain", M::SubSetItr) + io = pretty(io) + println(io, "Iterator for subsets") + println(io, Indent(), "of ", Set(M.b)) + print(io, Dedent(), "of length ", M.length) end #only subsets of a given size @@ -286,12 +555,17 @@ struct SubSetSizeItr{T} length::Int end +@doc raw""" + subsets(s::Set, k::Int) -> SubSetSizeItr{T} + +Return an iterator on all sub-sets of size `k` of `s`. +""" function subsets(s::Set{T}, k::Int) where T # subsets are represented by integers in the Combinatorial_number_system # https://en.wikipedia.org/wiki/Combinatorial_number_system # this iterator could indexed, the other one below not # maybe use "The coolest way to generate combinations" instead - b = collect(unique(s)) + b = unique(s) m = Int(binomial(length(b), k)) C = Vector{Vector{Int}}() while k >= 1 @@ -309,11 +583,9 @@ function subsets(s::Set{T}, k::Int) where T push!(C, B) k -=1 end - return SubSetSizeItr{T}(b, length(C), C, m) end - function int_to_elt(M::SubSetSizeItr{T}, i::Int) where T s = Set{T}() j = 1 @@ -326,7 +598,6 @@ function int_to_elt(M::SubSetSizeItr{T}, i::Int) where T while length(s) < M.k push!(s, M.b[length(s)]) end - return s end @@ -353,6 +624,16 @@ function Base.getindex(S::SubSetSizeItr, i::Int) return Hecke.int_to_elt(S, i) end -function Base.show(io::IO, M::SubSetSizeItr) - println(io, "subset iterator of length $(M.length) for $(M.b) and subsets of size $(M.k)") +function Base.show(io::IO, M::SubSetSizeItr{T}) where {T} + print(io, "Iterator for subsets of size $(M.k) of a set") + if !get(io, :supercompact, false) && T != Any + print(io, " with elements of type $T") + end +end + +function Base.show(io::IO, ::MIME"text/plain", M::SubSetSizeItr) + io = pretty(io) + println(io, "Iterator for subsets of size $(M.k)") + println(io, Indent(), "of ", Set(M.b)) + print(io, Dedent(), "of length ", M.length) end diff --git a/test/Misc.jl b/test/Misc.jl index 59ee89b5b3..c8795f52d6 100644 --- a/test/Misc.jl +++ b/test/Misc.jl @@ -12,4 +12,5 @@ include("Misc/UnitsModM.jl") include("Misc/Matrix.jl") include("Misc/PIDIdeal.jl") + include("Misc/MSet.jl") end diff --git a/test/Misc/MSet.jl b/test/Misc/MSet.jl new file mode 100644 index 0000000000..774fe24462 --- /dev/null +++ b/test/Misc/MSet.jl @@ -0,0 +1,93 @@ +@testset "Multi-sets" begin + str = "A nice sentence" + m = @inferred multiset(str) + io = IOBuffer() + show(io, MIME"text/plain"(), m) + @test length(String(take!(io))) == 92 + show(io, m) + @test length(String(take!(io))) == 44 + print(IOContext(io, :supercompact => true), m) + @test length(String(take!(io))) == 39 + + m = @inferred multiset(Int[x^3%8 for x = 1:50]) + @test !isempty(m) + @test length(m) == 50 + @test eltype(m) == Int + @test !(2 in m) + + m2 = copy(m) + @test m2 == m + @test unique(m) == unique(collect(m)) + + push!(m, 2, 4) + pop!(m, 2) + @test (2 in m) + @test multiplicity(m, 2) == 3 + + delete!(m, 2) + @test multiplicity(m, 2) == 0 + + @test pop!(m, 2, 0) == 0 + k = pop!(m2) + @test multiplicity(m2, k) == multiplicity(m, k) - 1 + + m = @inferred multiset(Dict("a" => 4, "b" => 1, "c" => 9)) + lis = @inferred collect(m) + + m2 = @inferred union(m, lis) + for i in m + @test multiplicity(m2, i) == 2*multiplicity(m, i) + end + + @test union(m) == m + @test length(filter(x -> multiplicity(m, x) != 1, m)) == length(m) - 1 + + m = multiset(Int[x^3%8 for x = 1:50]) + filter!(isodd, m) + @test length(m) == 25 + + val = @inferred multiplicities(m) + @test all(x -> x > 1, val) + + @test isempty(setdiff(m, m)) +end + +@testset "Sub-multi-set iterator" begin + m = @inferred multiset(["a", "b", "c"], [4, 1, 9]) + M = @inferred subsets(m) + io = IOBuffer() + show(io, MIME"text/plain"(), M) + @test length(String(take!(io))) == 61 + show(io, M) + @test length(String(take!(io))) == 64 + print(IOContext(io, :supercompact => true), M) + @test length(String(take!(io))) == 35 + @test eltype(M) == typeof(m) + @test length(collect(M)) == length(M) +end + +@testset "Sub-set iterators" begin + s = Set(String["a", "b", "c"]) + S = @inferred subsets(s) + io = IOBuffer() + show(io, MIME"text/plain"(), S) + @test length(String(take!(io))) == 58 + show(io, S) + @test length(String(take!(io))) == 58 + print(IOContext(io, :supercompact => true), S) + @test length(String(take!(io))) == 29 + @test eltype(S) == typeof(s) + @test length(collect(S)) == length(S) + + S = @inferred subsets(s, 2) + io = IOBuffer() + show(io, MIME"text/plain"(), S) + @test length(String(take!(io))) == 68 + show(io, S) + @test length(String(take!(io))) == 68 + print(IOContext(io, :supercompact => true), S) + @test length(String(take!(io))) == 39 + @test eltype(S) == typeof(s) + @test length(collect(S)) == length(S) + @test length(S[1]) == 2 +end From b163274806566f45e6513bad9415c7d8cfaf1b51 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 10 Oct 2023 12:31:04 +0200 Subject: [PATCH 2/7] fix oversize printing --- src/Misc/MSet.jl | 2 +- test/Misc/MSet.jl | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index f07bdcfce5..bfb55e8bdd 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -211,7 +211,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) end else lmax = maximum(length(sprint(show, a)) for a in un[1:szh]) - for i in 1:sz + for i in 1:szh println(io) k = un[i] lk = length(sprint(show, k)) diff --git a/test/Misc/MSet.jl b/test/Misc/MSet.jl index 774fe24462..d9c1f22758 100644 --- a/test/Misc/MSet.jl +++ b/test/Misc/MSet.jl @@ -9,6 +9,10 @@ print(IOContext(io, :supercompact => true), m) @test length(String(take!(io))) == 39 + M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) + show(io, MIME"text/plain"(), m) + @test length(String(take!(io))) == 983 + m = @inferred multiset(Int[x^3%8 for x = 1:50]) @test !isempty(m) @test length(m) == 50 From a0a87defc402150b3aeb4543a3627e9d3ed2795d Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 10 Oct 2023 17:15:15 +0200 Subject: [PATCH 3/7] fix union + add sum/intersect and difference --- docs/src/features/mset.md | 49 +++-- src/Misc/MSet.jl | 341 ++++++++++++++++++++++++++------- src/NumField/NfAbs/Elem.jl | 2 +- src/NumField/NfAbs/PolyFact.jl | 2 +- test/Misc/MSet.jl | 14 +- 5 files changed, 318 insertions(+), 90 deletions(-) diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md index 90ba63106e..3a3adc1588 100644 --- a/docs/src/features/mset.md +++ b/docs/src/features/mset.md @@ -27,21 +27,39 @@ multiset ### Functions -Existing functions for any collection of objects which are currently available +One can iterate over an `MSet` as on a regular `Set`. Here is moreover a list +of functions defined for collections of objects which are currently available for `MSet`: -* `similar` +* `==` +* `all` +* `any` +* `copy` +* `delete!` +* `eltype` +* `filter` +* `filter!` +* `in` +* `intersect` +* `intersect!` * `isempty` +* `issubset` * `length` -* `eltype` +* `pop!` * `push!` -* `copy` -* `==` +* `setdiff` +* `setdiff!` +* `similar` * `unique` +* `union` +* `union!` +* ... -One can also iterate over an `MSet`, and use `filter` for a given predicate on -the keys of the underlying dictionary (not on their values!). One can also test -containment. +Note that `pop!` and `delete!` for `MSet` are available but have a different behaviour. +For an element `x` in an multi-set `M <: MSet`, then `pop!(M, x)` will remove +*one* instance of `x` in `M` - in particular `multiplicity(M, x)` will drop by +$1$. Much stronger, `delete!(M, x)` will remove *all* instances of `x` in `M` and +so `multiplicity(M, x)` will be $0$. While `unique` will return the keys of the underlying dictionary, one can access the values (i.e. the multiplicities of the elements in the multi-set) via the @@ -52,16 +70,13 @@ multiplicities(::MSet) multiplicity(::MSet{T}, ::T) where T ``` -Note that `pop!` and `delete!` for `MSet` are available but have a different behaviour. -For an element `x` in an multi-set `M <: MSet`, then `pop!(M, x)` will remove -*one* instance of `x` in `M` - in particular `multiplicity(M, x)` will drop by -$1$. Much stronger, `delete!(M, x)` will remove *all* instances of `x` in `M` and -so `multiplicity(M, x)` will be $0$. +Finally, the sum and difference for `MSet` are also available. Difference is +given by the complements of sets and the sum is given by disjoint union of sets. -Finally, one can take unions (`union`, `union!`) of an `MSet` with other -finite collections of objects. Note that `union!` will require coercion of -elements. Similarly, one can compare an `MSet` with another collection with the -`setdiff/setdiff!` functions. +```@docs +sum(::MSet, ::Mset) +Base.:(-)(::MSet, ::MSet...) +``` ## Sub-set iterators diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index bfb55e8bdd..9dbc9c94a8 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -4,6 +4,14 @@ export multiplicity export multiset export subsets +############################################################################### +# +# Multi-sets +# +############################################################################### + +### Type and constructors + @doc raw""" MSet{T} <: AbstractSet{T} @@ -26,7 +34,15 @@ mutable struct MSet{T} <: AbstractSet{T} dict::Dict{T,Int} MSet{T}() where {T} = new{T}(Dict{T,Int}()) - MSet{T}(itr) where {T} = union!(new{T}(Dict{T,Int}()), itr) + + function MSet{T}(itr) where {T} + s = new{T}(Dict{T, Int}()) + for x in itr + push!(s, x) + end + return s + end + MSet{T}(d::Dict{T, Int}) where {T} = new{T}(d) MSet{T}(l::Vector{T}, m::Vector{Int}) where {T} = MSet{T}(Dict(zip(l, m))) end @@ -90,7 +106,7 @@ MSet{String} with 14 elements: "a" : 4 ``` """ -multiset(iter) = MSet(iter) +multiset(itr) = MSet(itr) function multiset(d::Dict{T, Int}) where {T} @req minimum(collect(values(d))) > 0 "The values of d must be positive integers" @@ -147,6 +163,10 @@ MSet{QQFieldElem}() Base.similar(::MSet{T}) where {T} = MSet{T}() Base.similar(::MSet, T::Type) = MSet{T}() +Base.copy(s::MSet) = union!(similar(s), s) + +### Show methods + # We try to adopt the same conventions as in Oscar, so one-line printing should # stay in one line, and we do not give details about what is in the MSet: the # detailled printing will take care of it @@ -190,9 +210,9 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) print(io, Indent()) d = s.dict un = collect(keys(d)) + rmax = maximum(ndigits(k) for k in values(d)) + offmax = szw - (rmax + 3) if length(un) <= szh - rmax = maximum(ndigits(k) for k in values(d)) - offmax = szw - (rmax + 3) lmax = min(maximum(length(sprint(show, a)) for a in un), offmax) for k in un pk = sprint(show, k) @@ -210,16 +230,21 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) end end else - lmax = maximum(length(sprint(show, a)) for a in un[1:szh]) + lmax = min(maximum(length(sprint(show, a)) for a in un[1:szh]), offmax) for i in 1:szh println(io) k = un[i] + pk = sprint(show, k) lk = length(sprint(show, k)) v = d[k] - if v > 1 - print(io, "$k", " "^(lmax-lk+1), ": $v") + if lk > offmax + print(io, pk[1:offmax-length(" \u2026")], " \u2026") else - print(io, "$k") + print(io, pk) + end + lk = min(offmax, lk) + if v > 1 + print(io, " "^(lmax-lk+1), ": $v") end end println(io) @@ -228,18 +253,42 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) end end +### Iteration + Base.isempty(s::MSet) = isempty(s.dict) Base.length(s::MSet) = sum(values(s.dict)) Base.IteratorSize(::Type{MSet}) = Base.HasLength() Base.IteratorEltype(::Type{MSet}) = Base.HasEltype() Base.eltype(::Type{MSet{T}}) where {T} = T -Base.in(x, s::MSet) = haskey(s.dict, x) +Base.in(x, s::MSet) = any(y -> x == y, keys(s.dict)) -function Base.push!(s::MSet, x, mult::Int=1) - add_to_key!(s.dict, x, mult) +function Base.iterate(s::MSet) + I = iterate(s.dict) + I === nothing && return I + return I[1][1], (I[1], I[2], 1) +end + +function Base.iterate(s::MSet, state) + if state[3] < state[1][2] + return state[1][1], (state[1], state[2], state[3]+1) + else + I = iterate(s.dict, state[2]) + I === nothing && return I + val, st = I + return (val[1], (val, st, 1)) + end +end + +### MSets operations + +function Base.push!(s::MSet{T}, x, mult::Int=1) where {T} + @req promote_type(T, typeof(x)) == T "Cannot coerce element" + y = x isa T ? x : T(x) + add_to_key!(s.dict, y, mult) end function Base.pop!(s::MSet{T}, x) where {T} + @req promote_type(T, typeof(x)) == T "Cannot coerce element" y = x isa T ? x : T(x) y in s || throw(KeyError(y)) add_to_key!(s.dict, y, -1) @@ -247,6 +296,7 @@ function Base.pop!(s::MSet{T}, x) where {T} end function Base.pop!(s::MSet{T}, x, default) where {T} + @req promote_type(T, typeof(x)) == T "Cannot coerce element" y = x isa T ? x : T(x) return y in s ? pop!(s, y) : (default isa T ? default : T(default)) end @@ -254,53 +304,227 @@ end Base.pop!(s::MSet) = (val = iterate(s.dict)[1][1]; pop!(s, val)) function Base.delete!(s::MSet{T}, x) where {T} + @req promote_type(T, typeof(x)) == T "Cannot coerce element" y = x isa T ? x : T(x) delete!(s.dict, y) return s end -Base.copy(s::MSet) = union!(similar(s), s) +Base.setdiff(s::MSet, itrs...) = setdiff!(copy(s), itrs...) -==(s1::MSet, s2::MSet) = s1.dict == s2.dict +function Base.setdiff!(s::MSet, itrs...) + for x in itr + setdiff!(s, itr) + end + return s +end -function Base.iterate(s::MSet) - I = iterate(s.dict) - I === nothing && return I - return I[1][1], (I[1], I[2], 1) +function Base.setdiff!(s::MSet, itr) + for x in itr + pop!(s, x, x) + end + return s end -function Base.iterate(s::MSet, state) - if state[3] < state[1][2] - return state[1][1], (state[1], state[2], state[3]+1) - else - I = iterate(s.dict, state[2]) - I === nothing && return I - val, st = I - return (val[1], (val, st, 1)) +@doc raw""" + Base.:(-)(s::MSet, itrs::MSet...) -> MSet + +Return the multi-set associated to the complement in `s` of the collections +in `itrs`. + +Alias for `setdiff(s, itrs...)`. + +# Examples +```jldoctest +julia> m = multiset("A very nice sentence") +MSet{Char} with 20 elements: + 'n' : 3 + 'e' : 5 + 'A' + 'y' + 'i' + 'r' + 's' + 't' + ' ' : 3 + 'c' : 2 + 'v' + +julia> n = multiset("A nice sentence") +MSet{Char} with 15 elements: + 'n' : 3 + 'A' + 'c' : 2 + 'i' + 'e' : 4 + 's' + 't' + ' ' : 2 + +julia> n-m +MSet{Char}() + +julia> m-n +MSet{Char} with 5 elements: + 'e' + 'y' + 'r' + ' ' + 'v' +``` +""" +Base.:(-)(s::MSet, itrs::MSet...) = setdiff(s, itrs...) + +function Base.unique(s::MSet) + return collect(keys(s.dict)) +end + +function Base.issubset(s1::MSet{T}, s2::MSet{U}) where {T, U} + @req promote_type(T, U) == U "Cannot compare multi-sets" + !issubset(U[convert(U, x) for x in keys(s1.dict)], unique(s2)) && return false + for x in unique(s2) + (multiplicity(s1, x) > multiplicity(s2, x)) && return false + end + return true +end + +@doc raw""" + Base.sum(s::MSet, itrs::MSet...) -> MSet + Base.:(+)(s::MSet, itrs::MSet...) -> MSet + +Return the multi-sets associated to the disjoint union of `s` and the +collections of objects in `itrs`. + +# Examples +```jldoctest +julia> m = multiset("A nice sentence") +MSet{Char} with 15 elements: + 'n' : 3 + 'A' + 'c' : 2 + 'i' + 'e' : 4 + 's' + 't' + ' ' : 2 + +julia> n = multiset("A very nice sentence") +MSet{Char} with 20 elements: + 'n' : 3 + 'e' : 5 + 'A' + 'y' + 'i' + 'r' + 's' + 't' + ' ' : 3 + 'c' : 2 + 'v' + +julia> m + n +MSet{Char} with 35 elements: + 'n' : 6 + 'e' : 9 + 'A' : 2 + 's' : 2 + 'i' : 2 + 't' : 2 + 'y' + 'r' + ' ' : 5 + 'c' : 4 + 'v' +``` +""" +function Base.sum(s1::MSet, s2::MSet) + T = Base.promote_eltype(s1, s2) + s = similar(s1, T) + d = s.dict + val = union(unique(s1), unique(s2)) + for x in val + d[x] = multiplicity(s1, x) + multiplicity(s2, x) end + return s +end + +function Base.sum(s::MSet, itrs::MSet...) + s2 = sum(s, itrs[1]) + return sum(s2, itrs[2:end]...) end +Base.:(+)(s::MSet, itrs::MSet...) = sum(s, itrs...) + Base.union(s::MSet) = copy(s) -function Base.union(s::MSet, sets...) - T = Base.promote_eltype(s, sets...) - u = MSet{T}() - union!(u, s) - for t in sets - union!(u, t) +function Base.union(s1::MSet, s2::MSet) + T = Base.promote_eltype(s1, s2) + s = similar(s1, T) + d = s.dict + val = union(unique(s1), unique(s2)) + for x in val + d[x] = max(multiplicity(s1, x), multiplicity(s2, x)) + end + return s +end + +function Base.union(s::MSet, itrs...) + s2 = union(s, multiset(itrs[1])) + return union(s2, itrs[2:end]...) +end + +function Base.union!(s1::MSet{T}, s2::MSet{U}) where {T, U} + @req promote_type(T, U) == T "Cannot coerce elements" + val = union(unique(s1), T[convert(T, x) for x in keys(s2.dict)]) + d = s1.dict + for x in val + d[x] = max(multiplicity(s1, x), multiplicity(s2, x)) end - return u + return s1 +end + +function Base.union!(s::MSet, itrs...) + union!(s, multiset(itrs[1])) + return union!(s, itrs[2:end]...) end -function Base.union!(s::MSet, xs) - T = eltype(s) - @req promote_type(T, eltype(xs)) == T "Cannot coerce elements" - for x in xs - push!(s, convert(T, x)) +function Base.intersect(s1::MSet, s2::MSet) + val = unique(s1) + filter!(x -> any(y -> x == y, keys(s2.dict)), val) + T = promote_type(eltype(s1), typeof.(val)...) + s = similar(s1, T) + d = s.dict + for x in val + d[x] = min(multiplicity(s1, x), multiplicity(s2, x)) end return s end +function Base.intersect(s::MSet, itrs...) + s2 = intersect(s, multiset(itrs[1])) + return intersect(s2, itrs[2:end]...) +end + +function Base.intersect!(s1::MSet{T}, s2::MSet) where {T} + val = unique(s1) + filter!(x -> any(y -> x == y, keys(s2.dict)), val) + @req promote_type(T, typeof.(val)...) == T "Cannot coerce elements" + d = s1.dict + for x in unique(s1) + if !(x in val) + delete!(s1, x) + else + d[x] = min(multiplicity(s1, x), multiplicity(s2, T(x))) + end + end + return s1 +end + +function Base.intersect!(s::MSet, itrs...) + s2 = intersect!(s, multiset(itrs[1])) + return intersect!(s2, itrs[2:end]...) +end + function Base.filter(pred, s::MSet) t = similar(s) for (x, m) in s.dict @@ -380,7 +604,8 @@ julia> multiplicity(m, 6) 0 ``` """ -function multiplicity(s::MSet{T}, x::T) where {T} +function multiplicity(s::MSet{T}, x) where {T} + @req promote_type(T, typeof(x)) == T "Cannot coerce element" y = x isa T ? x : T(x) if haskey(s.dict, y) return s.dict[y] @@ -389,29 +614,13 @@ function multiplicity(s::MSet{T}, x::T) where {T} end end -function Base.unique(s::MSet) - return collect(keys(s.dict)) -end - -Base.setdiff(s::MSet, itrs...) = setdiff!(copy(s), itrs...) - -function Base.setdiff!(s::MSet, itrs...) - for x in itr - setdiff!(s, itr) - end - return s -end - -function Base.setdiff!(s::MSet, itr) - for x in itr - pop!(s, x) - end - return s -end +############################################################################### +# +# Sub-set iterators +# +############################################################################### -############################################ -# subsets iterator -############################################ +### Sub-multi-sets struct MSubSetItr{T} b::Vector{T} @@ -430,12 +639,6 @@ function subsets(s::MSet{T}) where T # subset (bi, ni) -> sum ni gi where gi = prod (mj+1) b = unique(s) m = Int[multiplicity(s, x) for x in b] - #= not needed for the iterator - g = [1] - for i=2:length(b) - push!(g, g[end]*(m[i]+1)) - end - =# return MSubSetItr{T}(b, m, length(m) == 0 ? 1 : prod(x+1 for x in m)) end @@ -485,7 +688,8 @@ end #... to be completed from base/Set.jl ... -#subsets for Set +### Arbitrary sub-sets + struct SubSetItr{T} b::Vector{T} length::Int @@ -547,7 +751,8 @@ function Base.show(io::IO, ::MIME"text/plain", M::SubSetItr) print(io, Dedent(), "of length ", M.length) end -#only subsets of a given size +### Sub-sets of a given size + struct SubSetSizeItr{T} b::Vector{T} k::Int #subsets of size k only diff --git a/src/NumField/NfAbs/Elem.jl b/src/NumField/NfAbs/Elem.jl index ffbb961e5b..a4c5a8a469 100644 --- a/src/NumField/NfAbs/Elem.jl +++ b/src/NumField/NfAbs/Elem.jl @@ -462,7 +462,7 @@ function _ds(fa) @assert all(x->x == 1, values(fa.fac)) T = Int[degree(x) for x = keys(fa.fac)] M = MSet(T) - return Set(sum(s) for s = subsets(M) if length(s) > 0) + return Set(sum(collect(s)) for s = subsets(M) if length(s) > 0) end function _degset(f::ZZPolyRingElem, p::Int) diff --git a/src/NumField/NfAbs/PolyFact.jl b/src/NumField/NfAbs/PolyFact.jl index 2f59be21ef..dc9600182a 100644 --- a/src/NumField/NfAbs/PolyFact.jl +++ b/src/NumField/NfAbs/PolyFact.jl @@ -318,7 +318,7 @@ function degree_set(fa::Dict{Int, Int}) ind += v end M = MSet(T) - return Set(sum(s) for s = subsets(M) if length(s) > 0) + return Set(sum(collect(s)) for s = subsets(M) if length(s) > 0) end @doc raw""" diff --git a/test/Misc/MSet.jl b/test/Misc/MSet.jl index d9c1f22758..890bd254e6 100644 --- a/test/Misc/MSet.jl +++ b/test/Misc/MSet.jl @@ -10,8 +10,8 @@ @test length(String(take!(io))) == 39 M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) - show(io, MIME"text/plain"(), m) - @test length(String(take!(io))) == 983 + show(io, MIME"text/plain"(), M) + @test length(String(take!(io))) == 945 m = @inferred multiset(Int[x^3%8 for x = 1:50]) @test !isempty(m) @@ -37,12 +37,16 @@ m = @inferred multiset(Dict("a" => 4, "b" => 1, "c" => 9)) lis = @inferred collect(m) + @test length(m) == length(lis) - m2 = @inferred union(m, lis) + m2 = @inferred m + m for i in m @test multiplicity(m2, i) == 2*multiplicity(m, i) end + m3 = @inferred m-m + @test length(m3) == 0 + @test union(m) == m @test length(filter(x -> multiplicity(m, x) != 1, m)) == length(m) - 1 @@ -68,6 +72,10 @@ end @test length(String(take!(io))) == 35 @test eltype(M) == typeof(m) @test length(collect(M)) == length(M) + + n = collect(M)[end] + @test union(m, n) == m + @test intersect(m, n) == n end @testset "Sub-set iterators" begin From 24cfca822569b728d9ec5e4212dcae19f77c9e85 Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 10 Oct 2023 17:53:48 +0200 Subject: [PATCH 4/7] further fixes --- docs/src/features/mset.md | 4 +- src/Misc/MSet.jl | 79 +++++++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md index 3a3adc1588..205daaa33d 100644 --- a/docs/src/features/mset.md +++ b/docs/src/features/mset.md @@ -11,7 +11,7 @@ end ### Type and constructors -Objects of type `Mset` consists of a dictionary whose keys are the elements in +Objects of type `MSet` consists of a dictionary whose keys are the elements in the set, and the values are their respective multiplicity. ```@docs @@ -74,7 +74,7 @@ Finally, the sum and difference for `MSet` are also available. Difference is given by the complements of sets and the sum is given by disjoint union of sets. ```@docs -sum(::MSet, ::Mset) +sum(::MSet, ::MSet) Base.:(-)(::MSet, ::MSet...) ``` diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index 9dbc9c94a8..2f212ca452 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -382,8 +382,8 @@ end function Base.issubset(s1::MSet{T}, s2::MSet{U}) where {T, U} @req promote_type(T, U) == U "Cannot compare multi-sets" !issubset(U[convert(U, x) for x in keys(s1.dict)], unique(s2)) && return false - for x in unique(s2) - (multiplicity(s1, x) > multiplicity(s2, x)) && return false + for x in unique(s1) + (multiplicity(s1, x) > multiplicity(s2, convert(U, x))) && return false end return true end @@ -441,9 +441,12 @@ function Base.sum(s1::MSet, s2::MSet) T = Base.promote_eltype(s1, s2) s = similar(s1, T) d = s.dict - val = union(unique(s1), unique(s2)) - for x in val - d[x] = multiplicity(s1, x) + multiplicity(s2, x) + for x in unique(s1) + add_to_key!(d, convert(T, x), multiplicity(s1, x)) + end + + for y in unique(s2) + add_to_key!(d, convert(T, y), multiplicity(s2, y)) end return s end @@ -461,9 +464,23 @@ function Base.union(s1::MSet, s2::MSet) T = Base.promote_eltype(s1, s2) s = similar(s1, T) d = s.dict - val = union(unique(s1), unique(s2)) - for x in val - d[x] = max(multiplicity(s1, x), multiplicity(s2, x)) + un1 = unique(s1) + un2 = unique(s2) + for x in un1 + j = findfirst(y -> x == y, un2) + if j === nothing + d[convert(T, x)] = multiplicity(s1, x) + else + k = max(multiplicity(s1, x), multiplicity(s2, un2[j])) + d[convert(T, x)] = k + end + end + + for y in un2 + j = findfirst(x -> x == y, un1) + if j === nothing + d[convert(T, y)] = multiplicity(s2, y) + end end return s end @@ -475,10 +492,22 @@ end function Base.union!(s1::MSet{T}, s2::MSet{U}) where {T, U} @req promote_type(T, U) == T "Cannot coerce elements" - val = union(unique(s1), T[convert(T, x) for x in keys(s2.dict)]) + un1 = unique(s1) + un2 = unique(s2) d = s1.dict - for x in val - d[x] = max(multiplicity(s1, x), multiplicity(s2, x)) + for x in un1 + j = findfirst(y -> x == y, un2) + if j !== nothing + k = max(multiplicity(s1, x), multiplicity(s2, un2[j])) + d[x] = k + end + end + + for y in un2 + j = findfirst(x -> x == y, un1) + if j === nothing + d[convert(T, y)] = multiplicity(s2, y) + end end return s1 end @@ -489,13 +518,15 @@ function Base.union!(s::MSet, itrs...) end function Base.intersect(s1::MSet, s2::MSet) - val = unique(s1) - filter!(x -> any(y -> x == y, keys(s2.dict)), val) + un1 = unique(s1) + un2 = unique(s2) + val = filter(x -> any(y -> x == y, un2), un1) T = promote_type(eltype(s1), typeof.(val)...) s = similar(s1, T) d = s.dict for x in val - d[x] = min(multiplicity(s1, x), multiplicity(s2, x)) + y = un2[findfirst(y -> x == y, un2)] + d[T(x)] = min(multiplicity(s1, x), multiplicity(s2, y)) end return s end @@ -506,15 +537,17 @@ function Base.intersect(s::MSet, itrs...) end function Base.intersect!(s1::MSet{T}, s2::MSet) where {T} - val = unique(s1) - filter!(x -> any(y -> x == y, keys(s2.dict)), val) + un1 = unique(s1) + un2 = unique(s2) + val = filter(x -> any(y -> x == y, un2), un1) @req promote_type(T, typeof.(val)...) == T "Cannot coerce elements" d = s1.dict - for x in unique(s1) + for x in un1 if !(x in val) delete!(s1, x) else - d[x] = min(multiplicity(s1, x), multiplicity(s2, T(x))) + y = un2[findfirst(y -> x == y, un2)] + d[x] = min(multiplicity(s1, x), multiplicity(s2, y)) end end return s1 @@ -604,11 +637,11 @@ julia> multiplicity(m, 6) 0 ``` """ -function multiplicity(s::MSet{T}, x) where {T} - @req promote_type(T, typeof(x)) == T "Cannot coerce element" - y = x isa T ? x : T(x) - if haskey(s.dict, y) - return s.dict[y] +function multiplicity(s::MSet, x) + un = unique(s) + j = findfirst(y -> x == y, un) + if j !== nothing + return s.dict[un[j]] else return 0 end From 51ecc89aa6789231f7fad1083329a918a74f8baf Mon Sep 17 00:00:00 2001 From: StevellM Date: Tue, 10 Oct 2023 21:34:09 +0200 Subject: [PATCH 5/7] hopefully fix the checks + some improvements + more tests --- docs/src/features/mset.md | 2 +- src/Misc/MSet.jl | 74 ++++++++++++++++++--------------------- test/Misc/MSet.jl | 34 ++++++++++++++++-- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md index 205daaa33d..11d3beaa58 100644 --- a/docs/src/features/mset.md +++ b/docs/src/features/mset.md @@ -67,7 +67,7 @@ following functions: ```@docs multiplicities(::MSet) -multiplicity(::MSet{T}, ::T) where T +multiplicity(::MSet, ::Any) ``` Finally, the sum and difference for `MSet` are also available. Difference is diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index 2f212ca452..fb01ba8a04 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -203,7 +203,7 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) print(io,"()") else io = pretty(io) - szh, szw = displaysize(io) + szh, szw = displaysize() szh -= 5 szw -= 10 print(io, " with ", ItemQuantity(length(s), "element"), ":") @@ -313,8 +313,8 @@ end Base.setdiff(s::MSet, itrs...) = setdiff!(copy(s), itrs...) function Base.setdiff!(s::MSet, itrs...) - for x in itr - setdiff!(s, itr) + for x in itrs + setdiff!(s, x) end return s end @@ -382,8 +382,8 @@ end function Base.issubset(s1::MSet{T}, s2::MSet{U}) where {T, U} @req promote_type(T, U) == U "Cannot compare multi-sets" !issubset(U[convert(U, x) for x in keys(s1.dict)], unique(s2)) && return false - for x in unique(s1) - (multiplicity(s1, x) > multiplicity(s2, convert(U, x))) && return false + for (x, k) in s1.dict + (k > multiplicity(s2, convert(U, x))) && return false end return true end @@ -441,12 +441,12 @@ function Base.sum(s1::MSet, s2::MSet) T = Base.promote_eltype(s1, s2) s = similar(s1, T) d = s.dict - for x in unique(s1) - add_to_key!(d, convert(T, x), multiplicity(s1, x)) + for (x, k) in s1.dict + add_to_key!(d, convert(T, x), k) end - for y in unique(s2) - add_to_key!(d, convert(T, y), multiplicity(s2, y)) + for (y, k) in s2.dict + add_to_key!(d, convert(T, y), k) end return s end @@ -464,22 +464,20 @@ function Base.union(s1::MSet, s2::MSet) T = Base.promote_eltype(s1, s2) s = similar(s1, T) d = s.dict - un1 = unique(s1) - un2 = unique(s2) - for x in un1 - j = findfirst(y -> x == y, un2) - if j === nothing - d[convert(T, x)] = multiplicity(s1, x) + for (x, k) in s1.dict + fi1 = filter(y -> x == y[1], s2.dict) + if isempty(fi1) + d[convert(T, x)] = k else - k = max(multiplicity(s1, x), multiplicity(s2, un2[j])) + k = max(k, multiplicity(s2, first(fi1)[2])) d[convert(T, x)] = k end end - for y in un2 - j = findfirst(x -> x == y, un1) - if j === nothing - d[convert(T, y)] = multiplicity(s2, y) + for (y, k) in s2.dict + fi2 = filter(x -> x[1] == y, s1.dict) + if isempty(fi2) + d[convert(T, y)] = k end end return s @@ -495,18 +493,18 @@ function Base.union!(s1::MSet{T}, s2::MSet{U}) where {T, U} un1 = unique(s1) un2 = unique(s2) d = s1.dict - for x in un1 - j = findfirst(y -> x == y, un2) - if j !== nothing - k = max(multiplicity(s1, x), multiplicity(s2, un2[j])) + for (x, k) in s1.dict + fi1 = filter(y -> x == y[1], s2.dict) + if !isempty(fi1) + k = max(k, multiplicity(s2, first(fi1)[2])) d[x] = k end end - for y in un2 - j = findfirst(x -> x == y, un1) - if j === nothing - d[convert(T, y)] = multiplicity(s2, y) + for (y, k) in s2.dict + fi2 = filter(x -> x == y[1], s1.dict) + if isempty(fi2) + d[convert(T, y)] = k end end return s1 @@ -518,15 +516,13 @@ function Base.union!(s::MSet, itrs...) end function Base.intersect(s1::MSet, s2::MSet) - un1 = unique(s1) - un2 = unique(s2) - val = filter(x -> any(y -> x == y, un2), un1) + val = collect(filter(x -> any(y -> x == y, keys(s2.dict)), keys(s1.dict))) T = promote_type(eltype(s1), typeof.(val)...) s = similar(s1, T) d = s.dict for x in val - y = un2[findfirst(y -> x == y, un2)] - d[T(x)] = min(multiplicity(s1, x), multiplicity(s2, y)) + y = first(filter(y -> x == y[1], s2.dict)) + d[T(x)] = min(multiplicity(s1, x), y[2]) end return s end @@ -537,17 +533,15 @@ function Base.intersect(s::MSet, itrs...) end function Base.intersect!(s1::MSet{T}, s2::MSet) where {T} - un1 = unique(s1) - un2 = unique(s2) - val = filter(x -> any(y -> x == y, un2), un1) + val = collect(filter(x -> any(y -> x == y, keys(s2.dict)), keys(s1.dict))) @req promote_type(T, typeof.(val)...) == T "Cannot coerce elements" d = s1.dict - for x in un1 + for (x, k) in d if !(x in val) delete!(s1, x) else - y = un2[findfirst(y -> x == y, un2)] - d[x] = min(multiplicity(s1, x), multiplicity(s2, y)) + y = first(filter(y -> x == y[1], s2.dict)) + d[x] = min(k, y[2]) end end return s1 @@ -615,7 +609,7 @@ function multiplicities(s::MSet) end @doc raw""" - multiplicity(s::MSet{T}, x::T) -> Int + multiplicity(s::MSet, x) -> Int Return the multiplicity of the element `x` in the multi-set `s`. If `x` is not in `s`, return 0. diff --git a/test/Misc/MSet.jl b/test/Misc/MSet.jl index 890bd254e6..e140558814 100644 --- a/test/Misc/MSet.jl +++ b/test/Misc/MSet.jl @@ -9,9 +9,19 @@ print(IOContext(io, :supercompact => true), m) @test length(String(take!(io))) == 39 - M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) - show(io, MIME"text/plain"(), M) - @test length(String(take!(io))) == 945 + withenv("LINES" => 24, "COLUMNS" => 80) do + M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) + show(io, MIME"text/plain"(), M) + @test length(String(take!(io))) == 945 + + M = MSet{String}("$i"^100 for j in 1:4 for i in 1:130) + show(io, MIME"text/plain"(), M) + @test length(String(take!(io))) == 1422 + end + + m = MSet{Int}() + show(io, MIME"text/plain"(), m) + @test length(String(take!(io))) == 13 m = @inferred multiset(Int[x^3%8 for x = 1:50]) @test !isempty(m) @@ -58,6 +68,24 @@ @test all(x -> x > 1, val) @test isempty(setdiff(m, m)) + + m = MSet(Dict("a" => 4, "b" => 1, "c" => 9)) + @test length(setdiff!(m, unique(m), unique(m))) == 9 + m3 = sum(m, m, m) + @test length(m3) == 27 + m = multiset(Int[x^3%8 for x = 1:50]) + @test length(union(m, m3)) == 77 + @test union(m3, m3, m3) == m3 + + @test_throws ArgumentError union!(m, m3) + + m1 = multiset(fill(3,4)) + m2 = multiset(fill(2,6)) + m3 = multiset(Int[2,2,3,3,4,4]) + m4 = multiset(Int[3,4,4]) + @test isempty(intersect(m1, m2, m3)) + intersect!(m3, m1, m4) + @test length(m3) == 1 end @testset "Sub-multi-set iterator" begin From 24f07f81634b0afec5b35bb4f5de26aa5a99bb0d Mon Sep 17 00:00:00 2001 From: StevellM Date: Wed, 11 Oct 2023 10:24:50 +0200 Subject: [PATCH 6/7] apply suggestions and change what was requested --- docs/src/features/mset.md | 2 +- src/Misc/MSet.jl | 150 ++++++++++++--------------------- src/NumField/NfAbs/Elem.jl | 2 +- src/NumField/NfAbs/PolyFact.jl | 2 +- test/Misc/MSet.jl | 18 ++-- 5 files changed, 67 insertions(+), 107 deletions(-) diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md index 11d3beaa58..0ad2e03f14 100644 --- a/docs/src/features/mset.md +++ b/docs/src/features/mset.md @@ -74,7 +74,7 @@ Finally, the sum and difference for `MSet` are also available. Difference is given by the complements of sets and the sum is given by disjoint union of sets. ```@docs -sum(::MSet, ::MSet) +Base.:(+)(::MSet, ::MSet) Base.:(-)(::MSet, ::MSet...) ``` diff --git a/src/Misc/MSet.jl b/src/Misc/MSet.jl index fb01ba8a04..f9f9b5dc42 100644 --- a/src/Misc/MSet.jl +++ b/src/Misc/MSet.jl @@ -109,7 +109,7 @@ MSet{String} with 14 elements: multiset(itr) = MSet(itr) function multiset(d::Dict{T, Int}) where {T} - @req minimum(collect(values(d))) > 0 "The values of d must be positive integers" + @req minimum(values(d)) > 0 "The values of d must be positive integers" return MSet{T}(d) end @@ -122,7 +122,7 @@ end @doc raw""" multiset(T::Type) -> MSet{T} -Create an unitialized multi-set `M` with elements of type `T`. +Create an empty multi-set `M` with elements of type `T`. # Examples ```jldoctest @@ -138,10 +138,10 @@ multiset() = MSet() multiset(T::DataType) = MSet{T}() @doc raw""" - Base.similar(M::MSet{T}) -> MSet{T} - Base.similar(M::MSet, T::Type) -> MSet{T} + similar(M::MSet{T}) -> MSet{T} + similar(M::MSet, T::Type) -> MSet{T} -Create an unitialized multi-set with elements of type `T`. +Create an empty multi-set with elements of type `T`. # Examples ```jldoctest @@ -165,6 +165,9 @@ Base.similar(::MSet, T::Type) = MSet{T}() Base.copy(s::MSet) = union!(similar(s), s) +# Only for internal use +dict(s::MSet) = s.dict + ### Show methods # We try to adopt the same conventions as in Oscar, so one-line printing should @@ -182,7 +185,8 @@ function Base.show(io::IO, s::MSet{T}) where {T} else print(io, "MSet(") first = true - for (k, v) in s.dict + d = dict(s) + for (k, v) in d first || print(io, ", ") first = false if v > 1 @@ -208,16 +212,14 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) szw -= 10 print(io, " with ", ItemQuantity(length(s), "element"), ":") print(io, Indent()) - d = s.dict - un = collect(keys(d)) + d = dict(s) rmax = maximum(ndigits(k) for k in values(d)) offmax = szw - (rmax + 3) - if length(un) <= szh - lmax = min(maximum(length(sprint(show, a)) for a in un), offmax) - for k in un + if length(d) <= szh + lmax = min(maximum(length(sprint(show, a)) for a in keys(d)), offmax) + for (k, v) in d pk = sprint(show, k) lk = length(pk) - v = d[k] println(io) if lk > offmax print(io, pk[1:offmax-length(" \u2026")], " \u2026") @@ -230,10 +232,10 @@ function Base.show(io::IO, ::MIME"text/plain", s::MSet) end end else - lmax = min(maximum(length(sprint(show, a)) for a in un[1:szh]), offmax) - for i in 1:szh + un = collect(keys(d))[1:szh] + lmax = min(maximum(length(sprint(show, a)) for a in un), offmax) + for k in un println(io) - k = un[i] pk = sprint(show, k) lk = length(sprint(show, k)) v = d[k] @@ -260,7 +262,8 @@ Base.length(s::MSet) = sum(values(s.dict)) Base.IteratorSize(::Type{MSet}) = Base.HasLength() Base.IteratorEltype(::Type{MSet}) = Base.HasEltype() Base.eltype(::Type{MSet{T}}) where {T} = T -Base.in(x, s::MSet) = any(y -> x == y, keys(s.dict)) +Base.in(x::T, s::MSet{T}) where {T} = haskey(dict(s), x) +Base.in(x, s::MSet{T}) where {T} = haskey(dict(s), convert(T, x)) function Base.iterate(s::MSet) I = iterate(s.dict) @@ -298,7 +301,7 @@ end function Base.pop!(s::MSet{T}, x, default) where {T} @req promote_type(T, typeof(x)) == T "Cannot coerce element" y = x isa T ? x : T(x) - return y in s ? pop!(s, y) : (default isa T ? default : T(default)) + return y in s ? pop!(s, y) : (default isa T ? default : convert(T, default)) end Base.pop!(s::MSet) = (val = iterate(s.dict)[1][1]; pop!(s, val)) @@ -327,7 +330,7 @@ function Base.setdiff!(s::MSet, itr) end @doc raw""" - Base.:(-)(s::MSet, itrs::MSet...) -> MSet + (-)(s::MSet, itrs...) -> MSet Return the multi-set associated to the complement in `s` of the collections in `itrs`. @@ -373,24 +376,24 @@ MSet{Char} with 5 elements: 'v' ``` """ -Base.:(-)(s::MSet, itrs::MSet...) = setdiff(s, itrs...) +Base.:(-)(s::MSet, itrs...) = setdiff(s, itrs...) function Base.unique(s::MSet) - return collect(keys(s.dict)) + return collect(keys(dict(s))) end function Base.issubset(s1::MSet{T}, s2::MSet{U}) where {T, U} @req promote_type(T, U) == U "Cannot compare multi-sets" - !issubset(U[convert(U, x) for x in keys(s1.dict)], unique(s2)) && return false - for (x, k) in s1.dict - (k > multiplicity(s2, convert(U, x))) && return false + for (x, k) in dict(s1) + y = convert(U, x) + !haskey(dict(s2), y) && return false + k > multiplicity(s2, y) && return false end return true end @doc raw""" - Base.sum(s::MSet, itrs::MSet...) -> MSet - Base.:(+)(s::MSet, itrs::MSet...) -> MSet + (+)(s::MSet, itrs...) -> MSet Return the multi-sets associated to the disjoint union of `s` and the collections of objects in `itrs`. @@ -437,72 +440,45 @@ MSet{Char} with 35 elements: 'v' ``` """ -function Base.sum(s1::MSet, s2::MSet) +function Base.:(+)(s1::MSet, s2::MSet) T = Base.promote_eltype(s1, s2) s = similar(s1, T) - d = s.dict - for (x, k) in s1.dict + d = dict(s) + for (x, k) in dict(s1) add_to_key!(d, convert(T, x), k) end - for (y, k) in s2.dict + for (y, k) in dict(s2) add_to_key!(d, convert(T, y), k) end return s end -function Base.sum(s::MSet, itrs::MSet...) - s2 = sum(s, itrs[1]) - return sum(s2, itrs[2:end]...) +function Base.:(+)(s::MSet, itrs...) + s2 = s + multiset(itrs[1]) + return (+)(s2, itrs[2:end]...) end -Base.:(+)(s::MSet, itrs::MSet...) = sum(s, itrs...) - Base.union(s::MSet) = copy(s) -function Base.union(s1::MSet, s2::MSet) - T = Base.promote_eltype(s1, s2) - s = similar(s1, T) - d = s.dict - for (x, k) in s1.dict - fi1 = filter(y -> x == y[1], s2.dict) - if isempty(fi1) - d[convert(T, x)] = k - else - k = max(k, multiplicity(s2, first(fi1)[2])) - d[convert(T, x)] = k - end - end - - for (y, k) in s2.dict - fi2 = filter(x -> x[1] == y, s1.dict) - if isempty(fi2) - d[convert(T, y)] = k - end - end - return s -end - function Base.union(s::MSet, itrs...) - s2 = union(s, multiset(itrs[1])) - return union(s2, itrs[2:end]...) + T = Base.promote_eltype(s, itrs...) + return union!(similar(s, T), s, itrs...) end function Base.union!(s1::MSet{T}, s2::MSet{U}) where {T, U} @req promote_type(T, U) == T "Cannot coerce elements" - un1 = unique(s1) - un2 = unique(s2) - d = s1.dict - for (x, k) in s1.dict - fi1 = filter(y -> x == y[1], s2.dict) + d = dict(s1) + for (x, k) in d + fi1 = filter(isequal(x), keys(dict(s2))) if !isempty(fi1) - k = max(k, multiplicity(s2, first(fi1)[2])) + k = max(k, multiplicity(s2, first(fi1))) d[x] = k end end - for (y, k) in s2.dict - fi2 = filter(x -> x == y[1], s1.dict) + for (y, k) in dict(s2) + fi2 = filter(isequal(y), keys(d)) if isempty(fi2) d[convert(T, y)] = k end @@ -515,32 +491,20 @@ function Base.union!(s::MSet, itrs...) return union!(s, itrs[2:end]...) end -function Base.intersect(s1::MSet, s2::MSet) - val = collect(filter(x -> any(y -> x == y, keys(s2.dict)), keys(s1.dict))) - T = promote_type(eltype(s1), typeof.(val)...) - s = similar(s1, T) - d = s.dict - for x in val - y = first(filter(y -> x == y[1], s2.dict)) - d[T(x)] = min(multiplicity(s1, x), y[2]) - end - return s -end - function Base.intersect(s::MSet, itrs...) - s2 = intersect(s, multiset(itrs[1])) - return intersect(s2, itrs[2:end]...) + T = Base.promote_eltype(s, itrs...) + return intersect!(union!(similar(s, T), s), itrs...) end function Base.intersect!(s1::MSet{T}, s2::MSet) where {T} - val = collect(filter(x -> any(y -> x == y, keys(s2.dict)), keys(s1.dict))) + val = intersect(keys(dict(s1)), keys(dict(s2))) @req promote_type(T, typeof.(val)...) == T "Cannot coerce elements" - d = s1.dict + d = dict(s1) for (x, k) in d if !(x in val) delete!(s1, x) else - y = first(filter(y -> x == y[1], s2.dict)) + y = first(filter(y -> x == y[1], dict(s2))) d[x] = min(k, y[2]) end end @@ -554,7 +518,7 @@ end function Base.filter(pred, s::MSet) t = similar(s) - for (x, m) in s.dict + for (x, m) in dict(s) if pred(x) push!(t, x, m) end @@ -563,8 +527,7 @@ function Base.filter(pred, s::MSet) end function Base.filter!(pred, s::MSet) - un = unique(s) - for x in un + for x in keys(dict(s)) if !pred(x) delete!(s, x) end @@ -605,11 +568,11 @@ julia> collect(mult_m) ``` """ function multiplicities(s::MSet) - return values(s.dict) + return values(dict(s)) end @doc raw""" - multiplicity(s::MSet, x) -> Int + multiplicity(s::MSet{T}, x::T) -> Int Return the multiplicity of the element `x` in the multi-set `s`. If `x` is not in `s`, return 0. @@ -631,11 +594,10 @@ julia> multiplicity(m, 6) 0 ``` """ -function multiplicity(s::MSet, x) - un = unique(s) - j = findfirst(y -> x == y, un) - if j !== nothing - return s.dict[un[j]] +function multiplicity(s::MSet{T}, x::T) where {T} + y = x isa T ? x : T(x) + if haskey(dict(s), y) + return dict(s)[y] else return 0 end diff --git a/src/NumField/NfAbs/Elem.jl b/src/NumField/NfAbs/Elem.jl index a4c5a8a469..ffbb961e5b 100644 --- a/src/NumField/NfAbs/Elem.jl +++ b/src/NumField/NfAbs/Elem.jl @@ -462,7 +462,7 @@ function _ds(fa) @assert all(x->x == 1, values(fa.fac)) T = Int[degree(x) for x = keys(fa.fac)] M = MSet(T) - return Set(sum(collect(s)) for s = subsets(M) if length(s) > 0) + return Set(sum(s) for s = subsets(M) if length(s) > 0) end function _degset(f::ZZPolyRingElem, p::Int) diff --git a/src/NumField/NfAbs/PolyFact.jl b/src/NumField/NfAbs/PolyFact.jl index dc9600182a..2f59be21ef 100644 --- a/src/NumField/NfAbs/PolyFact.jl +++ b/src/NumField/NfAbs/PolyFact.jl @@ -318,7 +318,7 @@ function degree_set(fa::Dict{Int, Int}) ind += v end M = MSet(T) - return Set(sum(collect(s)) for s = subsets(M) if length(s) > 0) + return Set(sum(s) for s = subsets(M) if length(s) > 0) end @doc raw""" diff --git a/test/Misc/MSet.jl b/test/Misc/MSet.jl index e140558814..b37b63bcaf 100644 --- a/test/Misc/MSet.jl +++ b/test/Misc/MSet.jl @@ -9,15 +9,13 @@ print(IOContext(io, :supercompact => true), m) @test length(String(take!(io))) == 39 - withenv("LINES" => 24, "COLUMNS" => 80) do - M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) - show(io, MIME"text/plain"(), M) - @test length(String(take!(io))) == 945 - - M = MSet{String}("$i"^100 for j in 1:4 for i in 1:130) - show(io, MIME"text/plain"(), M) - @test length(String(take!(io))) == 1422 - end + M = MSet(root_lattice(:A, i) for j in 1:10 for i in 1:100) + show(io, MIME"text/plain"(), M) + s = String(take!(io)) + + M = MSet{String}("$i"^100 for j in 1:4 for i in 1:130) + show(io, MIME"text/plain"(), M) + s = String(take!(io)) m = MSet{Int}() show(io, MIME"text/plain"(), m) @@ -71,7 +69,7 @@ m = MSet(Dict("a" => 4, "b" => 1, "c" => 9)) @test length(setdiff!(m, unique(m), unique(m))) == 9 - m3 = sum(m, m, m) + m3 = m + m + m @test length(m3) == 27 m = multiset(Int[x^3%8 for x = 1:50]) @test length(union(m, m3)) == 77 From af4893b737130a58bd65c8d111bf4d25f29925fb Mon Sep 17 00:00:00 2001 From: Stevell Muller <78619134+StevellM@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:41:30 +0200 Subject: [PATCH 7/7] Update mset.md --- docs/src/features/mset.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/features/mset.md b/docs/src/features/mset.md index 0ad2e03f14..dabfa94ba2 100644 --- a/docs/src/features/mset.md +++ b/docs/src/features/mset.md @@ -67,7 +67,7 @@ following functions: ```@docs multiplicities(::MSet) -multiplicity(::MSet, ::Any) +multiplicity(::MSet{T}, ::T) where {T} ``` Finally, the sum and difference for `MSet` are also available. Difference is