Skip to content

Commit c2e846f

Browse files
committed
Merge branch 'master' of github.com:Workiva/go-datastructures into AddDependabot
2 parents 8696c4b + 18d7737 commit c2e846f

File tree

17 files changed

+402
-55
lines changed

17 files changed

+402
-55
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@Workiva/skreams

.github/workflows/tests.yaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: "Tests"
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- 'master'
8+
tags:
9+
- '*'
10+
11+
permissions:
12+
pull-requests: write
13+
contents: write
14+
id-token: write
15+
16+
jobs:
17+
Tests:
18+
runs-on: ubuntu-latest
19+
strategy:
20+
matrix:
21+
go: [ '1.15', 'stable' ]
22+
name: Tests on Go ${{ matrix.go }}
23+
steps:
24+
- name: Checkout Repo
25+
uses: actions/checkout@v4
26+
with:
27+
path: go/src/github.com/Workiva/go-datastructures
28+
29+
- name: Setup Go
30+
uses: actions/[email protected]
31+
with:
32+
go-version: ${{ matrix.go }}
33+
34+
# go install does not work because it needs credentials
35+
- name: install go2xunit
36+
run: |
37+
git clone https://github.com/tebeka/go2xunit.git
38+
cd go2xunit
39+
git checkout v1.4.10
40+
go install
41+
cd ..
42+
43+
- name: Run Tests
44+
timeout-minutes: 10
45+
run: |
46+
cd go/src/github.com/Workiva/go-datastructures
47+
go test ./... | tee ${{github.workspace}}/go-test.txt
48+
49+
- name: XML output
50+
run: |
51+
mkdir artifacts
52+
go2xunit -input ./go-test.txt -output ./artifacts/tests_go_version-${{ matrix.go }}.xml
53+
54+
- name: Upload Test Results
55+
uses: actions/upload-artifact@v4
56+
with:
57+
name: go-datastructures test go ${{ matrix.go }}
58+
path: ./artifacts/tests_go_version-${{ matrix.go }}.xml
59+
retention-days: 7
60+
61+
- uses: anchore/sbom-action@v0
62+
with:
63+
path: ./
64+
format: cyclonedx-json

.travis.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

Dockerfile

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
1-
FROM drydock-prod.workiva.net/workiva/smithy-runner-golang:121185 as build
1+
FROM golang:1.16-alpine3.13 AS build-go
22

33
ARG GIT_SSH_KEY
44
ARG KNOWN_HOSTS_CONTENT
55
WORKDIR /go/src/github.com/Workiva/go-datastructures/
66
ADD . /go/src/github.com/Workiva/go-datastructures/
77

8-
RUN mkdir /root/.ssh && \
9-
echo "$KNOWN_HOSTS_CONTENT" > "/root/.ssh/known_hosts" && \
10-
chmod 700 /root/.ssh/ && \
11-
umask 0077 && echo "$GIT_SSH_KEY" >/root/.ssh/id_rsa && \
12-
eval "$(ssh-agent -s)" && ssh-add /root/.ssh/id_rsa
13-
148
ARG GOPATH=/go/
159
ENV PATH $GOPATH/bin:$PATH
16-
RUN git config --global [email protected]:.insteadOf https://github.com
1710
RUN echo "Starting the script section" && \
18-
glide install && \
19-
echo "script section completed"
11+
go mod vendor && \
12+
echo "script section completed"
2013

21-
ARG BUILD_ARTIFACTS_DEPENDENCIES=/go/src/github.com/Workiva/go-datastructures/glide.lock
14+
ARG BUILD_ARTIFACTS_DEPENDENCIES=/go/src/github.com/Workiva/go-datastructures/go.mod
2215

2316
FROM scratch

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
go-datastructures
32
=================
43

@@ -150,7 +149,7 @@ interface and the most expensive operation in CPU profiling is the interface
150149
method which in turn calls into runtime.assertI2T. We need generics.
151150

152151
#### Immutable B Tree
153-
A btree based on two principals, immutablability and concurrency.
152+
A btree based on two principles, immutability and concurrency.
154153
Somewhat slow for single value lookups and puts, it is very fast for bulk operations.
155154
A persister can be injected to make this index persistent.
156155

@@ -229,6 +228,6 @@ Requirements to commit here:
229228

230229

231230
### Maintainers
232-
233-
- Dustin Hiatt <[[email protected]](mailto:[email protected])>
234231
- Alexander Campbell <[[email protected]](mailto:[email protected])>
232+
- Dustin Hiatt <[[email protected]](mailto:[email protected])>
233+
- Ryan Jackson <[[email protected]](mailto:[email protected])>

bitarray/bitarray.go

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ efficient way. This is *NOT* a threadsafe package.
2020
*/
2121
package bitarray
2222

23+
import "math/bits"
24+
2325
// bitArray is a struct that maintains state of a bit array.
2426
type bitArray struct {
2527
blocks []block
@@ -116,7 +118,74 @@ func (ba *bitArray) GetBit(k uint64) (bool, error) {
116118
return result, nil
117119
}
118120

119-
//ClearBit will unset a bit at the given index if it is set.
121+
// GetSetBits gets the position of bits set in the array.
122+
func (ba *bitArray) GetSetBits(from uint64, buffer []uint64) []uint64 {
123+
fromBlockIndex, fromOffset := getIndexAndRemainder(from)
124+
return getSetBitsInBlocks(
125+
fromBlockIndex,
126+
fromOffset,
127+
ba.blocks[fromBlockIndex:],
128+
nil,
129+
buffer,
130+
)
131+
}
132+
133+
// getSetBitsInBlocks fills a buffer with positions of set bits in the provided blocks. Optionally, indices may be
134+
// provided for sparse/non-consecutive blocks.
135+
func getSetBitsInBlocks(
136+
fromBlockIndex, fromOffset uint64,
137+
blocks []block,
138+
indices []uint64,
139+
buffer []uint64,
140+
) []uint64 {
141+
bufferCapacity := cap(buffer)
142+
if bufferCapacity == 0 {
143+
return buffer[:0]
144+
}
145+
146+
results := buffer[:bufferCapacity]
147+
resultSize := 0
148+
149+
for i, block := range blocks {
150+
blockIndex := fromBlockIndex + uint64(i)
151+
if indices != nil {
152+
blockIndex = indices[i]
153+
}
154+
155+
isFirstBlock := blockIndex == fromBlockIndex
156+
if isFirstBlock {
157+
block >>= fromOffset
158+
}
159+
160+
for block != 0 {
161+
trailing := bits.TrailingZeros64(uint64(block))
162+
163+
if isFirstBlock {
164+
results[resultSize] = uint64(trailing) + (blockIndex << 6) + fromOffset
165+
} else {
166+
results[resultSize] = uint64(trailing) + (blockIndex << 6)
167+
}
168+
resultSize++
169+
170+
if resultSize == cap(results) {
171+
return results[:resultSize]
172+
}
173+
174+
// Clear the bit we just added to the result, which is the last bit set in the block. Ex.:
175+
// block 01001100
176+
// ^block 10110011
177+
// (^block) + 1 10110100
178+
// block & (^block) + 1 00000100
179+
// block ^ mask 01001000
180+
mask := block & ((^block) + 1)
181+
block = block ^ mask
182+
}
183+
}
184+
185+
return results[:resultSize]
186+
}
187+
188+
// ClearBit will unset a bit at the given index if it is set.
120189
func (ba *bitArray) ClearBit(k uint64) error {
121190
if k >= ba.Capacity() {
122191
return OutOfRangeError(k)
@@ -137,6 +206,15 @@ func (ba *bitArray) ClearBit(k uint64) error {
137206
return nil
138207
}
139208

209+
// Count returns the number of set bits in this array.
210+
func (ba *bitArray) Count() int {
211+
count := 0
212+
for _, block := range ba.blocks {
213+
count += bits.OnesCount64(uint64(block))
214+
}
215+
return count
216+
}
217+
140218
// Or will bitwise or two bit arrays and return a new bit array
141219
// representing the result.
142220
func (ba *bitArray) Or(other BitArray) BitArray {

bitarray/bitarray_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
2324
)
2425

2526
func TestBitOperations(t *testing.T) {
@@ -142,6 +143,28 @@ func TestIsEmpty(t *testing.T) {
142143
assert.False(t, ba.IsEmpty())
143144
}
144145

146+
func TestCount(t *testing.T) {
147+
ba := newBitArray(500)
148+
assert.Equal(t, 0, ba.Count())
149+
150+
require.NoError(t, ba.SetBit(0))
151+
assert.Equal(t, 1, ba.Count())
152+
153+
require.NoError(t, ba.SetBit(40))
154+
require.NoError(t, ba.SetBit(64))
155+
require.NoError(t, ba.SetBit(100))
156+
require.NoError(t, ba.SetBit(200))
157+
require.NoError(t, ba.SetBit(469))
158+
require.NoError(t, ba.SetBit(500))
159+
assert.Equal(t, 7, ba.Count())
160+
161+
require.NoError(t, ba.ClearBit(200))
162+
assert.Equal(t, 6, ba.Count())
163+
164+
ba.Reset()
165+
assert.Equal(t, 0, ba.Count())
166+
}
167+
145168
func TestClear(t *testing.T) {
146169
ba := newBitArray(10)
147170

@@ -195,6 +218,53 @@ func BenchmarkGetBit(b *testing.B) {
195218
}
196219
}
197220

221+
func TestGetSetBits(t *testing.T) {
222+
ba := newBitArray(1000)
223+
buf := make([]uint64, 0, 5)
224+
225+
require.NoError(t, ba.SetBit(1))
226+
require.NoError(t, ba.SetBit(4))
227+
require.NoError(t, ba.SetBit(8))
228+
require.NoError(t, ba.SetBit(63))
229+
require.NoError(t, ba.SetBit(64))
230+
require.NoError(t, ba.SetBit(200))
231+
require.NoError(t, ba.SetBit(1000))
232+
233+
assert.Equal(t, []uint64(nil), ba.GetSetBits(0, nil))
234+
assert.Equal(t, []uint64{}, ba.GetSetBits(0, []uint64{}))
235+
236+
assert.Equal(t, []uint64{1, 4, 8, 63, 64}, ba.GetSetBits(0, buf))
237+
assert.Equal(t, []uint64{63, 64, 200, 1000}, ba.GetSetBits(10, buf))
238+
assert.Equal(t, []uint64{63, 64, 200, 1000}, ba.GetSetBits(63, buf))
239+
assert.Equal(t, []uint64{200, 1000}, ba.GetSetBits(128, buf))
240+
241+
require.NoError(t, ba.ClearBit(4))
242+
require.NoError(t, ba.ClearBit(64))
243+
assert.Equal(t, []uint64{1, 8, 63, 200, 1000}, ba.GetSetBits(0, buf))
244+
assert.Empty(t, ba.GetSetBits(1001, buf))
245+
246+
ba.Reset()
247+
assert.Empty(t, ba.GetSetBits(0, buf))
248+
}
249+
250+
func BenchmarkGetSetBits(b *testing.B) {
251+
numItems := uint64(168000)
252+
253+
ba := newBitArray(numItems)
254+
for i := uint64(0); i < numItems; i++ {
255+
if i%13 == 0 || i%5 == 0 {
256+
require.NoError(b, ba.SetBit(i))
257+
}
258+
}
259+
260+
buf := make([]uint64, 0, ba.Capacity())
261+
262+
b.ResetTimer()
263+
for i := 0; i < b.N; i++ {
264+
ba.GetSetBits(0, buf)
265+
}
266+
}
267+
198268
func TestEquality(t *testing.T) {
199269
ba := newBitArray(s + 1)
200270
other := newBitArray(s + 1)

bitarray/interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ type BitArray interface {
3636
// function returns an error if the position is out
3737
// of range. A sparse bit array never returns an error.
3838
GetBit(k uint64) (bool, error)
39+
// GetSetBits gets the position of bits set in the array. Will
40+
// return as many set bits as can fit in the provided buffer
41+
// starting from the specified position in the array.
42+
GetSetBits(from uint64, buffer []uint64) []uint64
3943
// ClearBit clears the bit at the given position. This
4044
// function returns an error if the position is out
4145
// of range. A sparse bit array never returns an error.
@@ -55,6 +59,8 @@ type BitArray interface {
5559
// in the case of a dense bit array or the highest possible
5660
// seen capacity of the sparse array.
5761
Capacity() uint64
62+
// Count returns the number of set bits in this array.
63+
Count() int
5864
// Or will bitwise or the two bitarrays and return a new bitarray
5965
// representing the result.
6066
Or(other BitArray) BitArray

0 commit comments

Comments
 (0)