Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/src/intset.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DataStructures.IntSet

`DataStructures.IntSet` is a drop-in replacement for the Base `BitSet`
`DataStructures.IntSet` (`<: AbstractSet{Int}`) is a drop-in replacement for the Base `BitSet`
type. It efficiently stores dense collections of small non-negative
`Int`s as a sorted set. The constructor `IntSet([itr])` constructs a
sorted set of the integers generated by the given iterable object, or an
Expand Down
2 changes: 2 additions & 0 deletions docs/src/sparse_int_set.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# DataStructures.SparseIntSet

`SparseIntSet` is an `AbstractSet{Int}` with a packed representation.

Implementation of a __Sparse Integer Set__, for background see [Sparse Sets](https://www.computist.xyz/2018/06/sparse-sets.html).
Only positive non-zero `Int`s are allowed inside the set.
The idea is to have one **packed** `Vector` storing all the `Int`s contained in the set as to allow for fast iteration, and a sparse, paged **reverse** `Vector` with the position of a particular `Int` inside the **packed** `Vector`. This allows for very fast iteration, insertion and deletion of indices.
Expand Down
6 changes: 5 additions & 1 deletion src/int_set.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# This file was a part of Julia. License is MIT: http://julialang.org/license

mutable struct IntSet
mutable struct IntSet <: AbstractSet{Int}
bits::BitVector
inverse::Bool
IntSet() = new(falses(256), false)
end
IntSet(itr) = union!(IntSet(), itr)

Base.empty(s::IntSet) = IntSet()
Base.emptymutable(::IntSet, ::Type{Int}=Int) = IntSet()
Base.copymutable(s::IntSet) = copy(s)
Base.filter!(f, s::IntSet) = Base.unsafe_filter!(f, s)
Base.copy(s1::IntSet) = copy!(IntSet(), s1)
function Base.copy!(to::IntSet, from::IntSet)
resize!(to.bits, length(from.bits))
Expand Down Expand Up @@ -133,6 +136,7 @@ end

Base.symdiff(s::IntSet, ns) = symdiff!(copy(s), ns)
Base.symdiff!(s::IntSet, ns) = (for n in ns; symdiff!(s, n); end; s)
Base.symdiff!(s::IntSet, ns::AbstractSet) = (for n in ns; symdiff!(s, n); end; s)
function Base.symdiff!(s::IntSet, n::Integer)
0 <= n < typemax(Int) || throw(ArgumentError(_intset_bounds_err_msg))
val = (n in s) ⊻ !s.inverse
Expand Down
7 changes: 6 additions & 1 deletion src/sparse_int_set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const INT_PER_PAGE = div(ccall(:jl_getpagesize, Clong, ()), sizeof(Int))
# we use this to mark pages not in use, it must never be written to.
const NULL_INT_PAGE = Vector{Int}()

mutable struct SparseIntSet
mutable struct SparseIntSet <: AbstractSet{Int}
packed ::Vector{Int}
reverse::Vector{Vector{Int}}
counters::Vector{Int} # counts the number of real elements in each page of reverse.
Expand All @@ -15,6 +15,9 @@ SparseIntSet(indices) = union!(SparseIntSet(), indices)
Base.eltype(::Type{SparseIntSet}) = Int

Base.empty(::SparseIntSet) = SparseIntSet()
Base.emptymutable(::SparseIntSet, ::Type{Int}=Int) = SparseIntSet()
Base.copymutable(s::SparseIntSet) = copy(s)
Base.filter!(f, s::SparseIntSet) = Base.unsafe_filter!(f, s)

function Base.empty!(s::SparseIntSet)
empty!(s.packed)
Expand Down Expand Up @@ -143,6 +146,7 @@ end
return in(id, s) ? (@inbounds pop!(s, id)) : default
end
Base.popfirst!(s::SparseIntSet) = pop!(s, first(s))
Base.delete!(s::SparseIntSet, id::Integer) = (pop!(s, id, nothing); s)

@inline Base.iterate(set::SparseIntSet, args...) = iterate(set.packed, args...)

Expand Down Expand Up @@ -170,6 +174,7 @@ Base.intersect!(s1::SparseIntSet, ss...) = intersect!(s1, intersect(ss...))

#Is there a more performant way to do this?
Base.intersect!(s1::SparseIntSet, ns) = copy!(s1, intersect(s1, ns))
Base.intersect!(s1::SparseIntSet, ns::AbstractSet) = copy!(s1, intersect(s1, ns))

Base.setdiff(s::SparseIntSet, ns) = setdiff!(copy(s), ns)
function Base.setdiff!(s::SparseIntSet, ns)
Expand Down
61 changes: 61 additions & 0 deletions test/test_int_set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,65 @@ import DataStructures: IntSet
show(IOBuffer(), complement(IntSet()))
end


@testset "AbstractSet" begin
@test IntSet([1, 2]) isa AbstractSet{Int}
@test IntSet() isa AbstractSet{Int}

# Generic AbstractSet algorithms dispatch on IntSet
@test issetequal(IntSet([1, 2]), IntSet([2, 1]))
@test isdisjoint(IntSet([1]), IntSet([2]))
@test !isdisjoint(IntSet([1, 2]), IntSet([2, 3]))
@test IntSet([1]) ⊆ Set([1, 2])
@test Set([1]) ⊆ IntSet([1, 2])

# Cross-type equality becomes available via AbstractSet fallbacks
@test IntSet([1, 2]) == Set([1, 2])
@test IntSet([1, 2]) == SparseIntSet([1, 2])

# Derived operations stay within the type rather than degrading to Set
@test filter(isodd, IntSet([1, 2, 3])) isa IntSet
@test filter(isodd, IntSet([1, 2, 3])) == IntSet([1, 3])
@test filter(_ -> false, IntSet([1, 2])) isa IntSet
@test isempty(filter(_ -> false, IntSet([1, 2])))
@test setdiff(IntSet([1, 2, 3]), [2]) isa IntSet
@test setdiff(IntSet([1, 2, 3]), [2]) == IntSet([1, 3])
@test setdiff(IntSet([1, 2, 3]), IntSet([2])) isa IntSet

# filter! / intersect! with AbstractSet (Base uses filter!(in(s2), s))
s = IntSet([1, 2, 3])
filter!(isodd, s)
@test s == IntSet([1, 3])
s = IntSet([1, 2, 3])
intersect!(s, Set([2, 3, 4]))
@test s == IntSet([2, 3])
s = IntSet([1])
union!(s, Set([2]))
@test s == IntSet([1, 2])

# Same-type equality and ordering still win on specificity
@test IntSet([1]) == IntSet([1])
@test IntSet([1]) < IntSet([1, 2])
@test IntSet([1, 2]) <= IntSet([1, 2])

# symdiff! with an AbstractSet argument resolves unambiguously
@test symdiff!(IntSet([1, 2, 3]), Set([2, 4])) == IntSet([1, 3, 4])

# Complement remains IntSet <: AbstractSet
c = complement(IntSet([0, 1, 2]))
@test c isa IntSet
@test c isa AbstractSet{Int}
@test 3 in c
@test !(1 in c)

# emptymutable / copymutable keep type
@test Base.emptymutable(IntSet([1])) isa IntSet
s = IntSet([1, 2])
cpy = Base.copymutable(s)
push!(cpy, 9)
@test 9 in cpy
@test !(9 in s)
end


end # @testset IntSet
47 changes: 46 additions & 1 deletion test/test_sparse_int_set.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using DataStructures, Test
import DataStructures: SparseIntSet
import DataStructures: SparseIntSet, IntSet

@testset "SparseIntSet" begin
@testset "Construction, collect" begin
Expand Down Expand Up @@ -189,4 +189,49 @@ import DataStructures: SparseIntSet
@test zip() isa Iterators.Zip # issue 621
end


@testset "AbstractSet" begin
@test SparseIntSet([1, 2]) isa AbstractSet{Int}
@test SparseIntSet() isa AbstractSet{Int}

@test issetequal(SparseIntSet([1, 2]), SparseIntSet([2, 1]))
@test isdisjoint(SparseIntSet([1]), SparseIntSet([2]))
@test !isdisjoint(SparseIntSet([1, 2]), SparseIntSet([2, 3]))
@test SparseIntSet([1]) ⊆ Set([1, 2])
@test Set([1]) ⊆ SparseIntSet([1, 2])

@test SparseIntSet([1, 2]) == Set([1, 2])
@test SparseIntSet([1, 2]) == IntSet([1, 2])

@test filter(isodd, SparseIntSet([1, 2, 3])) isa SparseIntSet
@test issetequal(filter(isodd, SparseIntSet([1, 2, 3])), SparseIntSet([1, 3]))
@test filter(_ -> false, SparseIntSet([1, 2])) isa SparseIntSet
@test isempty(filter(_ -> false, SparseIntSet([1, 2])))
@test setdiff(SparseIntSet([1, 2, 3]), [2]) isa SparseIntSet
@test issetequal(setdiff(SparseIntSet([1, 2, 3]), [2]), SparseIntSet([1, 3]))

s = SparseIntSet([1, 2, 3])
filter!(isodd, s)
@test issetequal(s, SparseIntSet([1, 3]))
@test issetequal(intersect!(SparseIntSet([1, 2, 3]), Set([2, 3, 4])), SparseIntSet([2, 3]))
s = SparseIntSet([1])
union!(s, Set([2]))
@test issetequal(s, SparseIntSet([1, 2]))

@test SparseIntSet([1]) == SparseIntSet([1])
@test SparseIntSet([1]) < SparseIntSet([1, 2])
@test SparseIntSet([1, 2]) <= SparseIntSet([1, 2])

# No native symdiff; AbstractSet fallback must still work
@test issetequal(symdiff(SparseIntSet([1, 2]), Set([2, 3])), Set([1, 3]))

@test Base.emptymutable(SparseIntSet([1])) isa SparseIntSet
s = SparseIntSet([1, 2])
cpy = Base.copymutable(s)
push!(cpy, 9)
@test 9 in cpy
@test !(9 in s)
end


end
Loading