forked from jakobnissen/StackCollections.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e4168d9
commit 3f2bdc4
Showing
6 changed files
with
126 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,43 @@ | ||
# SmallBitSet.jl | ||
Stack-allocated integer sets in Julia | ||
# StackCollections.jl | ||
Fixed-bit collections in Julia | ||
|
||
A small toy project to show the conciseness, abstraction and speed of Julia. | ||
This package implements a few collection types that can be stored in one or a few machine integers: | ||
|
||
This module implements one custom type - the `StackSet{M}`. A `StackSet{M}` is a set of integers in 0:(M-1) encoded in the lower M bits of a machine integer. | ||
|
||
In 121 lines, this module implements: | ||
|
||
* some basic methods: `copy`, `empty`, `isempty`, custom iteration, `length`, `minimum`, `maximum`, `last`, `in`, `push`, `delete` and `pop`. | ||
* some set-specific methods: `allunique`, `union`, `intersect`, `setdiff`, `complement` and `isdisjoint`. | ||
* As well as converters and constructors. | ||
|
||
A `StackSet` behaves exactly like you would expect an `AbstractSet` to do - other than that it is immutable: | ||
* `DigitSet`: A set of integers 0:63 | ||
* `StackSet`: A set of integers N:N+63 | ||
* `StackVector{L}`: A boolean vector with a length of up to 64. | ||
|
||
The main features of the types are: | ||
* They are simple to use, implements the basic methods from `Base` you would expect such as `union` for sets and `reverse` for vectors: | ||
``` | ||
julia> a = StackSet(2i+3 for i in 2:3:16) | ||
StackSet{64}([7,13,19,25,31]) | ||
julia> 7 in a ? length(union(a, a)) : length(intersect(a, complement(a))) | ||
5 | ||
julia> a = StackVector{4}([true, true, false, true]); reverse(a) | ||
4-element StackVector{4}: | ||
1 | ||
0 | ||
1 | ||
1 | ||
``` | ||
* They are safe by default, and throws informative error messages if you attempt illegal or undefined operations. | ||
``` | ||
|
||
It is safe to use: | ||
julia> push(DigitSet(), 100) | ||
ERROR: ArgumentError: DigitSet can only contain 0:63 | ||
``` | ||
julia> pop(a, 6) | ||
ERROR: KeyError: key 6 not found | ||
Stacktrace: | ||
[1] pop(::Main.SmallBitSet.StackSet{64}, ::Int64) at /home/jakni/Documents/scripts/play/SmallBitSet.jl/src/SmallBitSet.jl:100 | ||
[2] top-level scope at none:0 | ||
julia> push(a, -1) | ||
ERROR: ArgumentError: StackSet{64} can only contain 0:63 | ||
Stacktrace: | ||
[1] throw_StackSet_digit_err(::Val{64}) at /home/jakni/Documents/scripts/play/SmallBitSet.jl/src/SmallBitSet.jl:21 | ||
[2] push(::Main.SmallBitSet.StackSet{64}, ::Int64) at /home/jakni/Documents/scripts/play/SmallBitSet.jl/src/SmallBitSet.jl:90 | ||
[3] top-level scope at none:0 | ||
* All types are immutable and so easier to reason about. Base methods that usually end with an exclamation mark such as `push!` instead must use `push`. | ||
``` | ||
|
||
And is *extremely* efficiently implemented, with most set operations done in single clock cycles: | ||
|
||
julia> push!(DigitSet(), 100) | ||
ERROR: MethodError: no method matching push!(::DigitSet, ::Int64) | ||
``` | ||
julia> a, b = StackSet{11}(1:3:12), StackSet{41}(4:7:40); | ||
julia> f(x, y) = length(setdiff(x, complement(y))); | ||
julia> f(a, b) | ||
1 | ||
* They are _highly_ efficiently implemented, with most methods meticulously crafted for maximal performance. | ||
``` | ||
julia> f(x, y) = length(setdiff(x, symdiff(x, y))); | ||
julia> code_native(f, Tuple{typeof(a), typeof(b)}, debuginfo=:none) | ||
.text | ||
julia> code_native(f, (DigitSet, DigitSet), debuginfo=:none) | ||
.section __TEXT,__text,regular,pure_instructions | ||
movq (%rsi), %rax | ||
andq (%rdi), %rax | ||
popcntq %rax, %rax | ||
retq | ||
nopl (%rax) | ||
``` | ||
|
||
This API follows SemVer 2.0.0. The API for this package is defined by the documentation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
module t | ||
module StackCollections | ||
|
||
struct Unsafe end | ||
const unsafe = Unsafe() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
random_set() = DigitSet(rand(UInt)) | ||
|
||
|
||
@testset "Construction" begin | ||
@test DigitSet() === DigitSet() | ||
s = DigitSet([5, 1, 9, 8]) | ||
s2 = DigitSet([9, 8, 1, 5]) | ||
@test s === s2 | ||
|
||
@test_throws ArgumentError DigitSet([-1, 5, 12]) | ||
@test DigitSet([1, Int(5), 12]) === DigitSet([1, 12, 5, UInt(12)]) | ||
@test_throws ArgumentError DigitSet([5, UInt8(12), 64]) | ||
@test DigitSet([5, 12, 64]) === DigitSet([12, 5, 12, 5, 12]) | ||
end | ||
|
||
@testset "Basic" begin | ||
e = DigitSet() | ||
@test isempty(e) | ||
@test DigitSet() === e | ||
@test empty(e) === e | ||
@test empty(random_set()) === e | ||
|
||
s = DigitSet([7, 28, 41, 11]) | ||
s2 = DigitSet([7, 28, 41, 12]) | ||
s3 = DigitSet([7, 28, 12]) | ||
@test s != s2 | ||
@test s != s3 | ||
@test length(e) == 0 | ||
@test length(s) == 4 | ||
@test length(s2) == 4 | ||
@test length(s3) == 3 | ||
end | ||
|
||
@testset "Membership" begin | ||
for i in -1:64 | ||
test !(i in DigitSet()) | ||
end | ||
|
||
s = DigitSet([51, 11, 6, 32, 1, 0, 40]) | ||
s2 = Set(DigitSet) | ||
for i in 0:63 | ||
if i in s2 | ||
@test (i in s) | ||
else | ||
@test !(i in s) | ||
end | ||
end | ||
|
||
@test (51 in s) | ||
@test !(50 in s) | ||
|
||
@test !(5 in DigitSet()) | ||
@test !(0 in DigitSet()) | ||
|
||
#= | ||
Iteration | ||
Membership (in), minimum, maximum | ||
Modification | ||
push | ||
filter | ||
pop | ||
delete | ||
Set operations | ||
issubset | ||
isdisjoint | ||
union | ||
intersect | ||
symdiff | ||
setdiff | ||
=# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module TestStackCollections | ||
|
||
using Test | ||
using StackCollections | ||
|
||
@testset "DigitSet" begin | ||
include("digitset.jl") | ||
end | ||
|
||
@testset "StackSet" begin | ||
include("stackset.jl") | ||
end | ||
|
||
@testset "StackVector" begin | ||
include("stackvector.jl") | ||
end | ||
|
||
end # module |