Improve bit_array (#7665)

Fix `index: uint` -> `index: int`, examples.
Add `create_from_enum` helper.
This commit is contained in:
Jeroen van Rijn authored and GitHub committed 2026-09-29 15:54:42 +02:00
1 parent d7da8c0833
commit da7371404e
2 files changed
+105 -70

No files matched your search

+87 -53
View File
@@ -3,9 +3,7 @@ package container_dynamic_bit_array
import "base:builtin"
import "base:intrinsics"
/*
Note that these constants are dependent on the backing being a u64.
*/
// Note that these constants are dependent on the backing being a u64.
@(private="file")
INDEX_SHIFT :: 6
@@ -27,27 +25,29 @@ Bit_Array_Iterator :: struct {
word_idx: int,
bit_idx: uint,
}
/*
Wraps a `Bit_Array` into an Iterator
Wraps a `Bit_Array` into an `Bit_Array_Iterator`.
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
Returns:
- it: Iterator struct
- it: `Bit_Array_Iterator`
*/
make_iterator :: proc (ba: ^Bit_Array) -> (it: Bit_Array_Iterator) {
return Bit_Array_Iterator { array = ba }
}
/*
Returns the next bit, including its set-state. ok=false once exhausted
Returns the next bit, including its set-state. ok=false once exhausted.
Inputs:
- it: The iterator that holds the state.
Returns:
- set: `true` if the bit at `index` is set.
- index: The next bit of the Bit_Array referenced by `it`.
- index: The next bit of the `Bit_Array` referenced by `it`.
- ok: `true` if the iterator can continue, `false` if the iterator is done
*/
iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) {
@@ -65,34 +65,37 @@ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok:
return set, index, true
}
/*
Returns the next Set Bit, for example if `0b1010`, then the iterator will return index={1, 3} over two calls.
Returns the next *set* bit, for example if `0b1010`, then the iterator will return index={1, 3} over two calls.
Inputs:
- it: The iterator that holds the state.
Returns:
- index: The next *set* bit of the Bit_Array referenced by `it`.
- index: The next *set* bit of the `Bit_Array` referenced by `it`.
- ok: `true` if the iterator can continue, `false` if the iterator is done
*/
iterate_by_set :: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) {
return iterate_internal_(it, true)
return iterate_internal(it, true)
}
/*
Returns the next Unset Bit, for example if `0b1010`, then the iterator will return index={0, 2} over two calls.
Returns the next *unset* bit, for example if `0b1010`, then the iterator will return index={0, 2} over two calls.
Inputs:
- it: The iterator that holds the state.
Returns:
- index: The next *unset* bit of the Bit_Array referenced by `it`.
- index: The next *unset* bit of the `Bit_Array` referenced by `it`.
- ok: `true` if the iterator can continue, `false` if the iterator is done
*/
iterate_by_unset:: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) {
return iterate_internal_(it, false)
return iterate_internal(it, false)
}
/*
Iterates through set/unset bits
Iterates through set/unset bits.
*Private*
@@ -101,11 +104,11 @@ Inputs:
- ITERATE_SET_BITS: `true` for returning only set bits, false for returning only unset bits
Returns:
- index: The next *unset* bit of the Bit_Array referenced by `it`.
- index: The next *unset* bit of the `Bit_Array` referenced by `it`.
- ok: `true` if the iterator can continue, `false` if the iterator is done
*/
@(private="file")
iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) {
iterate_internal :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) {
word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0
when ! ITERATE_SET_BITS { word = ~word }
@@ -137,19 +140,20 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) ->
}
return index, index < it.array.length + it.array.bias
}
/*
Gets the state of a bit in the bit-array
Gets the state of a bit in the `Bit_Array`.
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
Returns:
- res: `true` if the bit at `index` is set.
- ok: Whether the index was valid. Returns `false` if the index is smaller than the bias.
*/
get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #optional_ok {
idx := int(index) - ba.bias
get :: proc(ba: ^Bit_Array, #any_int index: int) -> (res: bool, ok: bool) #optional_ok {
idx := index - ba.bias
if ba == nil || int(index) < ba.bias { return false, false }
@@ -167,28 +171,30 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #opti
return res, true
}
/*
Gets the state of a bit in the bit-array
Gets the state of a bit in the `Bit_Array`.
*Bypasses all Checks*
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
Returns:
- `true` if bit is set
*/
unsafe_get :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint) -> bool #no_bounds_check {
return bool((ba.bits[index >> INDEX_SHIFT] >> uint(index & INDEX_MASK)) & 1)
unsafe_get :: #force_inline proc(ba: ^Bit_Array, #any_int index: int) -> bool #no_bounds_check {
return bool((ba.bits[index >> INDEX_SHIFT] >> (uint(index) & INDEX_MASK)) & 1)
}
/*
Sets the state of a bit in the bit-array
Sets the state of a bit in the `Bit_Array`.
*Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)*
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
- set_to: `true` sets the bit on, `false` to turn it off
- allocator: (default is context.allocator)
@@ -196,9 +202,9 @@ Inputs:
Returns:
- ok: Whether the set was successful, `false` on allocation failure or bad index
*/
set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator := context.allocator) -> (ok: bool) {
set :: proc(ba: ^Bit_Array, #any_int index: int, set_to: bool = true, allocator := context.allocator) -> (ok: bool) {
idx := int(index) - ba.bias
idx := index - ba.bias
if ba == nil || int(index) < ba.bias { return false }
context.allocator = allocator
@@ -218,41 +224,44 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator
return true
}
/*
Sets the state of a bit in the bit-array
Sets the state of a bit in the `Bit_Array`.
*Bypasses all checks*
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
*/
unsafe_set :: proc(ba: ^Bit_Array, bit: int) #no_bounds_check {
ba.bits[bit >> INDEX_SHIFT] |= 1 << uint(bit & INDEX_MASK)
}
/*
Unsets the state of a bit in the bit-array. (Convienence wrapper for `set`)
Unsets the state of a bit in the `Bit_Array`. (Convienence wrapper for `set`)
*Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)*
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
- allocator: (default is context.allocator)
Returns:
- ok: Whether the unset was successful, `false` on allocation failure or bad index
*/
unset :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) {
unset :: #force_inline proc(ba: ^Bit_Array, #any_int index: int, allocator := context.allocator) -> (ok: bool) {
return set(ba, index, false, allocator)
}
/*
Unsets the state of a bit in the bit-array
*Bypasses all Checks*
/*
Unsets the state of a bit in the `Bit_Array`.
*Bypasses all checks*
Inputs:
- ba: Pointer to the Bit_Array
- ba: Pointer to the `Bit_Array`
- index: Which bit in the array
*/
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
@@ -260,7 +269,7 @@ unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
}
/*
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
A helper function to create a `Bit_Array` with optional bias, in case your smallest index is non-zero (including negative).
The range of bits created by this procedure is `min_index..<max_index`, and the
array will be able to expand beyond `max_index` if needed.
@@ -273,10 +282,11 @@ Inputs:
- allocator: (default is context.allocator)
Returns:
- ba: Allocates a bit_Array, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64).
- ba: Allocates a `Bit_Array`, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64).
*/
create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator, loc := #caller_location) -> (res: ^Bit_Array, ok: bool) #optional_ok {
size_in_bits := max_index - min_index
assert(max_index >= min_index, loc=loc)
if size_in_bits < 0 { return {}, false }
@@ -290,7 +300,27 @@ create :: proc(max_index: int, min_index: int = 0, allocator := context.allocato
}
/*
A helper function to initialize a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
A helper function to create a `Bit_Array` from an enum `E`.
The range of bits created by this procedure is `min(E)..<max(E)`, and the
array will be able to expand beyond `max(E)` if needed.
*Allocates (`new(Bit_Array) & make(ba.bits)`)*
Inputs:
- e: an `enum`
- min_index: minimum starting index (used as a bias)
- allocator: (default is context.allocator)
Returns:
- ba: Allocates a `Bit_Array`, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64).
*/
create_from_enum :: proc($T: typeid, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) where intrinsics.type_is_enum(T) #optional_ok {
return create(int(max(T)), int(min(T)), allocator)
}
/*
A helper function to initialize a `Bit_Array` with optional bias, in case your smallest index is non-zero (including negative).
The range of bits created by this procedure is `min_index..<max_index`, and the
array will be able to expand beyond `max_index` if needed.
@@ -321,20 +351,21 @@ init :: proc(res: ^Bit_Array, max_index: int, min_index: int = 0, allocator := c
}
/*
Sets all values in the Bit_Array to zero.
Sets all values in the `Bit_Array` to zero.
Inputs:
- ba: The target Bit_Array
- ba: The target `Bit_Array`
*/
clear :: proc(ba: ^Bit_Array) {
if ba == nil { return }
intrinsics.mem_zero(raw_data(ba.bits), builtin.len(ba.bits) * NUM_BITS / 8)
}
/*
Gets the length of set and unset valid bits in the Bit_Array.
Gets the length of set and unset valid bits in the `Bit_Array`.
Inputs:
- ba: The target Bit_Array
- ba: The target `Bit_Array`
Returns:
- length: The length of valid bits.
@@ -343,11 +374,12 @@ len :: proc(ba: ^Bit_Array) -> (length: int) {
if ba == nil { return }
return ba.length
}
/*
Shrinks the Bit_Array's backing storage to the smallest possible size.
Shrinks the `Bit_Array`'s backing storage to the smallest possible size.
Inputs:
- ba: The target Bit_Array
- ba: The target `Bit_Array`
*/
shrink :: proc(ba: ^Bit_Array) #no_bounds_check {
if ba == nil { return }
@@ -372,22 +404,24 @@ shrink :: proc(ba: ^Bit_Array) #no_bounds_check {
resize(&ba.bits, legs_needed)
builtin.shrink(&ba.bits)
}
/*
Deallocates the Bit_Array and its backing storage
Deallocates the `Bit_Array` and its backing storage
Inputs:
- ba: The target Bit_Array
- ba: The target `Bit_Array`
*/
destroy :: proc(ba: ^Bit_Array) {
if ba == nil { return }
delete(ba.bits)
if ba.free_pointer { // Only free if this Bit_Array was created using `create`, not when on the stack.
if ba.free_pointer { // Only free if this `Bit_Array` was created using `create`, not when on the stack.
free(ba)
}
}
/*
Resizes the Bit Array. For internal use. Provisions needed capacity+1
If you want to reserve the memory for a given-sized Bit Array up front, you can use `create`.
Resizes the `Bit_Array`. For internal use. Provisions needed capacity+1
If you want to reserve the memory for a given-sized `Bit_Array` up front, you can use `create`.
*/
@(private="file")
resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocator) -> (ok: bool) {
+18 -17
View File
@@ -8,20 +8,18 @@ Example:
package test
import "core:fmt"
import "core:container/bit_array"
import ba "core:container/bit_array"
main :: proc() {
using bit_array
bits: Bit_Array
bits: ba.Bit_Array
// returns `true`
fmt.println(set(&bits, 42))
fmt.println(ba.set(&bits, 42))
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
was_set, was_retrieved := ba.get(&bits, -1)
fmt.println(was_set, was_retrieved)
destroy(&bits)
ba.destroy(&bits)
}
A `Bit_Array` can optionally allow for negative indices, if the minimum value was given during creation.
@@ -29,7 +27,7 @@ Example:
package test
import "core:fmt"
import "core:container/bit_array"
import ba "core:container/bit_array"
main :: proc() {
Foo :: enum int {
@@ -38,17 +36,20 @@ Example:
Leaves = 69105,
}
using bit_array
bits := ba.create_from_enum(Foo)
defer ba.destroy(bits)
bits := create(int(max(Foo)), int(min(Foo)))
defer destroy(bits)
assert(bits.bias == int(Foo.Negative_Test))
assert(bits.length == abs(int(min(Foo))) + int(max(Foo)))
fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar))
fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar))
fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test))
fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves))
fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test))
fmt.printf("Freed.\n")
fmt.printfln("Set(Bar): %v", ba.set(bits, Foo.Bar))
fmt.printfln("Get(Bar): %v", ba.get(bits, Foo.Bar))
fmt.printfln("Set(Negative_Test): %v", ba.set(bits, Foo.Negative_Test))
fmt.printfln("Get(Leaves): %v", ba.get(bits, Foo.Leaves))
fmt.printfln("Get(Leaves): %v", ba.unsafe_get(bits, Foo.Leaves))
fmt.printfln("Get(Negative_Test): %v", ba.get(bits, Foo.Negative_Test))
fmt.printfln("Unset(Negative_Test): %v", ba.unset(bits, Foo.Negative_Test))
assert(ba.get(bits, Foo.Negative_Test) == false)
}
*/
package container_dynamic_bit_array