Skip to content

Commit

Permalink
Merge pull request #133 from Theodus/master
Browse files Browse the repository at this point in the history
Bitmap
  • Loading branch information
dustinhiatt-wf committed Apr 4, 2016
2 parents 6980a5f + e9712ca commit 93a32f6
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 104 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.out
*.test
*.test
.idea
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ Bitarray used to detect existence without having to resort to hashing with
hashmaps. Requires entities have a uint64 unique identifier. Two
implementations exist, regular and sparse. Sparse saves a great deal of space
but insertions are O(log n). There are some useful functions on the BitArray
interface to detect intersection between two bitarrays.
interface to detect intersection between two bitarrays. This package also
includes bitmaps of length 32 and 64 that provide increased speed and O(1) for
all operations by storing the bitmaps in unsigned integers rather than arrays.

#### Futures

Expand Down
87 changes: 87 additions & 0 deletions bitarray/bitmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
Copyright (c) 2016, Theodore Butler
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Package bitmap contains bitmaps of length 32 and 64 for tracking bool
// values without the need for arrays or hashing.
package bitarray

// Bitmap32 tracks 32 bool values within a uint32
type Bitmap32 uint32

// SetBit returns a Bitmap32 with the bit at the given position set to 1
func (b Bitmap32) SetBit(pos uint) Bitmap32 {
return b | (1 << pos)
}

// ClearBit returns a Bitmap32 with the bit at the given position set to 0
func (b Bitmap32) ClearBit(pos uint) Bitmap32 {
return b & ^(1 << pos)
}

// GetBit returns true if the bit at the given position in the Bitmap32 is 1
func (b Bitmap32) GetBit(pos uint) bool {
return (b & (1 << pos)) != 0
}

// PopCount returns the amount of bits set to 1 in the Bitmap32
func (b Bitmap32) PopCount() int {
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
b -= (b >> 1) & 0x55555555
b = (b>>2)&0x33333333 + b&0x33333333
b += b >> 4
b &= 0x0f0f0f0f
b *= 0x01010101
return int(byte(b >> 24))
}

// Bitmap64 tracks 64 bool values within a uint64
type Bitmap64 uint64

// SetBit returns a Bitmap64 with the bit at the given position set to 1
func (b Bitmap64) SetBit(pos uint) Bitmap64 {
return b | (1 << pos)
}

// ClearBit returns a Bitmap64 with the bit at the given position set to 0
func (b Bitmap64) ClearBit(pos uint) Bitmap64 {
return b & ^(1 << pos)
}

// GetBit returns true if the bit at the given position in the Bitmap64 is 1
func (b Bitmap64) GetBit(pos uint) bool {
return (b & (1 << pos)) != 0
}

// PopCount returns the amount of bits set to 1 in the Bitmap64
func (b Bitmap64) PopCount() int {
// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
b -= (b >> 1) & 0x5555555555555555
b = (b>>2)&0x3333333333333333 + b&0x3333333333333333
b += b >> 4
b &= 0x0f0f0f0f0f0f0f0f
b *= 0x0101010101010101
return int(byte(b >> 56))
}
105 changes: 105 additions & 0 deletions bitarray/bitmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright (c) 2016, Theodore Butler
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package bitarray

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestBitmap32_PopCount(t *testing.T) {
b := []uint32{
uint32(0x55555555), // 0x55555555 = 01010101 01010101 01010101 01010101
uint32(0x33333333), // 0x33333333 = 00110011 00110011 00110011 00110011
uint32(0x0F0F0F0F), // 0x0F0F0F0F = 00001111 00001111 00001111 00001111
uint32(0x00FF00FF), // 0x00FF00FF = 00000000 11111111 00000000 11111111
uint32(0x0000FFFF), // 0x0000FFFF = 00000000 00000000 11111111 11111111
}
for _, x := range b {
assert.Equal(t, 16, Bitmap32(x).PopCount())
}
}

func TestBitmap64_PopCount(t *testing.T) {
b := []uint64{
uint64(0x5555555555555555),
uint64(0x3333333333333333),
uint64(0x0F0F0F0F0F0F0F0F),
uint64(0x00FF00FF00FF00FF),
uint64(0x0000FFFF0000FFFF),
}
for _, x := range b {
assert.Equal(t, 32, Bitmap64(x).PopCount())
}
}

func TestBitmap32_SetBit(t *testing.T) {
m := Bitmap32(0)
assert.Equal(t, Bitmap32(0x4), m.SetBit(2))
}

func TestBitmap32_ClearBit(t *testing.T) {
m := Bitmap32(0x4)
assert.Equal(t, Bitmap32(0), m.ClearBit(2))
}

func TestBitmap32_zGetBit(t *testing.T) {
m := Bitmap32(0x55555555)
assert.Equal(t, true, m.GetBit(2))
}

func TestBitmap64_SetBit(t *testing.T) {
m := Bitmap64(0)
assert.Equal(t, Bitmap64(0x4), m.SetBit(2))
}

func TestBitmap64_ClearBit(t *testing.T) {
m := Bitmap64(0x4)
assert.Equal(t, Bitmap64(0), m.ClearBit(2))
}

func TestBitmap64_GetBit(t *testing.T) {
m := Bitmap64(0x55555555)
assert.Equal(t, true, m.GetBit(2))
}

func BenchmarkBitmap32_PopCount(b *testing.B) {
m := Bitmap32(0x33333333)
b.ResetTimer()
for i := b.N; i > 0; i-- {
m.PopCount()
}
}

func BenchmarkBitmap64_PopCount(b *testing.B) {
m := Bitmap64(0x3333333333333333)
b.ResetTimer()
for i := b.N; i > 0; i-- {
m.PopCount()
}
}
30 changes: 24 additions & 6 deletions trie/dtrie/dtrie.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ type Dtrie struct {
hasher func(v interface{}) uint32
}

type entry struct {
hash uint32
key interface{}
value interface{}
}

func (e *entry) KeyHash() uint32 {
return e.hash
}

func (e *entry) Key() interface{} {
return e.key
}

func (e *entry) Value() interface{} {
return e.value
}

// New creates an empty DTrie with the given hashing function.
// If nil is passed in, the default hashing function will be used.
func New(hasher func(v interface{}) uint32) *Dtrie {
Expand All @@ -59,16 +77,16 @@ func (d *Dtrie) Size() (size int) {
return size
}

// Get returns the Entry for the associated key or returns nil if the
// Get returns the value for the associated key or returns nil if the
// key does not exist.
func (d *Dtrie) Get(key interface{}) Entry {
return get(d.root, d.hasher(key), key)
func (d *Dtrie) Get(key interface{}) interface{} {
return get(d.root, d.hasher(key), key).Value()
}

// Insert adds an entry to the Dtrie, replacing the existing value if
// Insert adds a key value pair to the Dtrie, replacing the existing value if
// the key already exists and returns the resulting Dtrie.
func (d *Dtrie) Insert(entry Entry) *Dtrie {
root := insert(d.root, entry)
func (d *Dtrie) Insert(key, value interface{}) *Dtrie {
root := insert(d.root, &entry{d.hasher(key), key, value})
return &Dtrie{root, d.hasher}
}

Expand Down
46 changes: 5 additions & 41 deletions trie/dtrie/dtrie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,54 +27,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package dtrie

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestPopCount(t *testing.T) {
b := []uint32{
uint32(0x55555555), // 0x55555555 = 01010101 01010101 01010101 01010101
uint32(0x33333333), // 0x33333333 = 00110011 00110011 00110011 00110011
uint32(0x0F0F0F0F), // 0x0F0F0F0F = 00001111 00001111 00001111 00001111
uint32(0x00FF00FF), // 0x00FF00FF = 00000000 11111111 00000000 11111111
uint32(0x0000FFFF), // 0x0000FFFF = 00000000 00000000 11111111 11111111
}
for _, x := range b {
assert.Equal(t, 16, popCount(x))
}
}

func TestDefaultHasher(t *testing.T) {
assert.Equal(t,
defaultHasher(map[int]string{11234: "foo"}),
defaultHasher(map[int]string{11234: "foo"}))
assert.NotEqual(t, defaultHasher("foo"), defaultHasher("bar"))
}

type testEntry struct {
hash uint32
key int
value int
}

func (e *testEntry) KeyHash() uint32 {
return e.hash
}

func (e *testEntry) Key() interface{} {
return e.key
}

func (e *testEntry) Value() interface{} {
return e.value
}

func (e *testEntry) String() string {
return fmt.Sprint(e.value)
}

func collisionHash(key interface{}) uint32 {
return uint32(0xffffffff) // for testing collisions
}
Expand All @@ -87,7 +51,7 @@ func TestInsert(t *testing.T) {
func insertTest(t *testing.T, hashfunc func(interface{}) uint32, count int) *node {
n := emptyNode(0, 32)
for i := 0; i < count; i++ {
n = insert(n, &testEntry{hashfunc(i), i, i})
n = insert(n, &entry{hashfunc(i), i, i})
}
return n
}
Expand Down Expand Up @@ -130,7 +94,7 @@ func TestUpdate(t *testing.T) {
func updateTest(t *testing.T, hashfunc func(interface{}) uint32, count int) {
n := insertTest(t, hashfunc, count)
for i := 0; i < count; i++ {
n = insert(n, &testEntry{hashfunc(i), i, -i})
n = insert(n, &entry{hashfunc(i), i, -i})
}
}

Expand All @@ -152,7 +116,7 @@ func TestIterate(t *testing.T) {
close(stop)
}
}
assert.Equal(t, c, 100)
assert.True(t, c == 100)
// test with collisions
n = insertTest(t, collisionHash, 1000)
c = 0
Expand All @@ -174,7 +138,7 @@ func BenchmarkInsert(b *testing.B) {
n := emptyNode(0, 32)
b.ResetTimer()
for i := b.N; i > 0; i-- {
n = insert(n, &testEntry{defaultHasher(i), i, i})
n = insert(n, &entry{defaultHasher(i), i, i})
}
}

Expand All @@ -201,6 +165,6 @@ func BenchmarkUpdate(b *testing.B) {
n := insertTest(nil, defaultHasher, b.N)
b.ResetTimer()
for i := b.N; i > 0; i-- {
n = insert(n, &testEntry{defaultHasher(i), i, -i})
n = insert(n, &entry{defaultHasher(i), i, -i})
}
}
Loading

0 comments on commit 93a32f6

Please sign in to comment.