Skip to content

Commit 2adcf75

Browse files
committed
Basic bitmap implementation
1 parent da9868d commit 2adcf75

6 files changed

Lines changed: 351 additions & 0 deletions

File tree

.github/workflows/test.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Go package
2+
3+
on: [push]
4+
5+
jobs:
6+
test:
7+
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v3
11+
12+
- name: Set up Go
13+
uses: actions/setup-go@v3
14+
with:
15+
go-version: 1.19
16+
17+
- name: Test
18+
run: go test ./...

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Bitmap
2+
3+
Yet another bitmap implementation written in go.
4+
5+
## Installation
6+
7+
```
8+
$ go get -v github.com/f1monkey/bitmap
9+
```
10+
11+
## Usage
12+
13+
```go
14+
func main() {
15+
var b bitmap.Bitmap
16+
17+
b.IsEmpty() // true
18+
19+
b.Set(0)
20+
b.Has(0) // true
21+
b.Remove(0)
22+
b.Has(0) // false
23+
24+
b.Xor(1000)
25+
b.Has(1000) // true
26+
b.Xor(1000)
27+
b.Has(1000) // false
28+
29+
b2 := b.Clone() // copy of "b"
30+
31+
b2.Range(func(n uint32) bool {
32+
fmt.PrintLn(n)
33+
return true
34+
})
35+
}
36+
```

bitmap.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package bitmap
2+
3+
import "math/bits"
4+
5+
type Bitmap []uint64
6+
7+
// Set set n-th bit to 1
8+
func (b *Bitmap) Set(n uint32) {
9+
block, bit := n>>6, n%64
10+
b.grow(block)
11+
(*b)[block] |= (1 << bit)
12+
}
13+
14+
// Remove set n-th bit to 0
15+
func (b *Bitmap) Remove(n uint32) {
16+
block, bit := n>>6, n%64
17+
if uint32(len(*b)) <= block {
18+
return
19+
}
20+
(*b)[block] &= (0 << bit)
21+
}
22+
23+
// Xor invert n-th bit
24+
func (b *Bitmap) Xor(n uint32) {
25+
block, val := n>>6, n%64
26+
b.grow(block)
27+
(*b)[block] ^= (1 << val)
28+
}
29+
30+
// IsEmpty check if the bitmap has any bit set to 1
31+
func (b *Bitmap) IsEmpty() bool {
32+
for i := range *b {
33+
if (*b)[i] > 0 {
34+
return false
35+
}
36+
}
37+
38+
return true
39+
}
40+
41+
// Has check if n-th bit is set to 1
42+
func (b *Bitmap) Has(n uint32) bool {
43+
block, val := n>>6, n%64
44+
if uint32(len(*b)) <= block {
45+
return false
46+
}
47+
48+
return (*b)[block]&(1<<val) > 0
49+
}
50+
51+
// CountDiff count different bits in two bitmaps
52+
func (b *Bitmap) CountDiff(b2 Bitmap) int {
53+
diff := 0
54+
max := len(*b)
55+
if len(b2) > max {
56+
max = len(b2)
57+
}
58+
59+
for i := 0; i < max; i++ {
60+
if len(b2) <= i {
61+
diff += bits.OnesCount64((*b)[i])
62+
continue
63+
}
64+
if len(*b) <= i {
65+
diff += bits.OnesCount64((b2)[i])
66+
continue
67+
}
68+
69+
diff += bits.OnesCount64((*b)[i] ^ b2[i])
70+
}
71+
72+
return diff
73+
}
74+
75+
// Clone create a copy of the bitmap
76+
func (b *Bitmap) Clone() Bitmap {
77+
clone := make(Bitmap, len(*b))
78+
copy(clone, *b)
79+
80+
return clone
81+
}
82+
83+
// Range call the passed callback with all bits set to 1.
84+
// If the callback returns false, the method exits
85+
func (b *Bitmap) Range(f func(n uint32) bool) {
86+
for i, block := range *b {
87+
for block != 0 {
88+
tz := bits.TrailingZeros64(block)
89+
bitIndex := uint32(i*64 + tz)
90+
91+
if !f(bitIndex) {
92+
return
93+
}
94+
95+
block &= block - 1
96+
}
97+
}
98+
}
99+
100+
func (b *Bitmap) grow(length uint32) {
101+
if length+1 > uint32(len(*b)) {
102+
*b = append(*b, make(Bitmap, length+1-uint32(len(*b)))...)
103+
}
104+
}

bitmap_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package bitmap
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func Benchmark_Bitmap_Set(b *testing.B) {
11+
var bm Bitmap
12+
for i := 0; i < b.N; i++ {
13+
bm.Set(1)
14+
}
15+
}
16+
17+
func Test_Bitmap_Set(t *testing.T) {
18+
t.Run("must set the specified bit", func(t *testing.T) {
19+
t.Run("0", func(t *testing.T) {
20+
var b Bitmap
21+
b.Set(0)
22+
assert.Equal(t, Bitmap{1}, b)
23+
})
24+
t.Run("2", func(t *testing.T) {
25+
var b Bitmap
26+
b.Set(2)
27+
assert.Equal(t, Bitmap{4}, b)
28+
})
29+
t.Run("0,2", func(t *testing.T) {
30+
var b Bitmap
31+
b.Set(0)
32+
b.Set(2)
33+
assert.Equal(t, Bitmap{5}, b)
34+
})
35+
36+
t.Run("63", func(t *testing.T) {
37+
var b Bitmap
38+
b.Set(63)
39+
assert.Equal(t, Bitmap{9223372036854775808}, b)
40+
})
41+
42+
t.Run("64", func(t *testing.T) {
43+
var b Bitmap
44+
b.Set(64)
45+
assert.Equal(t, Bitmap{0, 1}, b)
46+
})
47+
})
48+
t.Run("must do nothing if the specified bit is already == 1", func(t *testing.T) {
49+
var b Bitmap
50+
b.Set(0)
51+
b.Set(0)
52+
assert.Equal(t, Bitmap{1}, b)
53+
})
54+
}
55+
56+
func Test_Bitmap_Remove(t *testing.T) {
57+
var b Bitmap
58+
59+
b.Remove(0)
60+
assert.Nil(t, b)
61+
62+
b.Set(0)
63+
b.Set(100)
64+
b.Remove(100)
65+
assert.Equal(t, Bitmap{1, 0}, b)
66+
}
67+
68+
func Benchmark_Bitmap_Xor(b *testing.B) {
69+
var bm Bitmap
70+
for i := 0; i < b.N; i++ {
71+
bm.Xor(1)
72+
}
73+
}
74+
75+
func Test_Bitmap_Xor(t *testing.T) {
76+
t.Run("must invert the specified bit", func(t *testing.T) {
77+
var b Bitmap
78+
b.Xor(0)
79+
assert.Equal(t, Bitmap{1}, b)
80+
b.Xor(0)
81+
assert.Equal(t, Bitmap{0}, b)
82+
})
83+
}
84+
85+
func Test_Bitmap_IsEmpty(t *testing.T) {
86+
t.Run("must return true if it's empty", func(t *testing.T) {
87+
var b Bitmap
88+
assert.True(t, b.IsEmpty())
89+
b.Xor(1)
90+
b.Xor(1)
91+
assert.True(t, b.IsEmpty())
92+
})
93+
t.Run("must return false if it is not empty", func(t *testing.T) {
94+
var b Bitmap
95+
b.Set(1)
96+
assert.False(t, b.IsEmpty())
97+
})
98+
}
99+
100+
func Test_Bitmap_Has(t *testing.T) {
101+
var b Bitmap
102+
assert.False(t, b.Has(0))
103+
b.Xor(0)
104+
assert.True(t, b.Has(0))
105+
b.Xor(0)
106+
assert.False(t, b.Has(0))
107+
}
108+
109+
func Benchmark_Bitmap_CountDiff(b *testing.B) {
110+
var b1, b2 Bitmap
111+
b1.Set(1)
112+
b1.Set(2)
113+
b2.Set(31)
114+
for i := 0; i < b.N; i++ {
115+
b1.CountDiff(b2)
116+
}
117+
}
118+
119+
func Test_Bitmap_CountDiff(t *testing.T) {
120+
t.Run("must return 0 if bitmaps are equal", func(t *testing.T) {
121+
var b1, b2 Bitmap
122+
b1.Set(1)
123+
b2.Set(1)
124+
assert.Equal(t, 0, b1.CountDiff(b2))
125+
})
126+
127+
t.Run("must return correct count of different bits", func(t *testing.T) {
128+
var b1, b2 Bitmap
129+
b1.Set(1)
130+
b1.Set(2)
131+
b1.Set(64)
132+
133+
b2.Set(31)
134+
b2.Set(64)
135+
b2.Set(650)
136+
137+
assert.Equal(t, 4, b1.CountDiff(b2))
138+
})
139+
}
140+
141+
func Test_Bitmap_Clone(t *testing.T) {
142+
var b1 Bitmap
143+
b1.Set(0)
144+
b2 := b1.Clone()
145+
146+
assert.Equal(t, b1, b2)
147+
148+
b2.Set(2)
149+
assert.Equal(t, Bitmap{5}, b2)
150+
assert.Equal(t, Bitmap{1}, b1)
151+
}
152+
153+
func Test_Bitmap_Range(t *testing.T) {
154+
var b1 Bitmap
155+
b1.Set(0)
156+
b1.Set(1)
157+
b1.Set(2)
158+
b1.Set(1000)
159+
b1.Set(10000)
160+
161+
var items []uint32
162+
b1.Range(func(n uint32) bool {
163+
items = append(items, n)
164+
if n == 1000 {
165+
return false
166+
}
167+
168+
return true
169+
})
170+
171+
require.Equal(t, []uint32{0, 1, 2, 1000}, items)
172+
}

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/f1monkey/bitmap
2+
3+
go 1.19
4+
5+
require github.com/stretchr/testify v1.8.4
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/pmezard/go-difflib v1.0.0 // indirect
10+
gopkg.in/yaml.v3 v3.0.1 // indirect
11+
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)