Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

bitset.gno

4.70 Kb · 189 lines
  1// Package bitset is a dense, fixed-capacity bit vector as a pure, reusable
  2// package: a compact set of small non-negative integers with the usual
  3// set algebra (union, intersection, difference).
  4//
  5// Storage is a []uint64 of ceil(n/64) words, so 1024 bits cost 16 words rather
  6// than 1024 booleans — the reason to reach for this on chain, where every byte
  7// is paid for.
  8//
  9// Capacity is fixed at construction and every operation is bounds-checked
 10// rather than growing: an out-of-range index is a caller bug, and silently
 11// growing would make gas unpredictable.
 12//
 13// A live demo of this package is at
 14// [r/moul/x/daily/bitsetdemo](/r/moul/x/daily/bitsetdemo/v0).
 15package bitset
 16
 17import "strings"
 18
 19// MaxBits bounds a BitSet so allocation and iteration stay predictable.
 20const MaxBits = 1 << 16 // 65536 bits = 1024 words = 8 KiB
 21
 22// BitSet is a fixed-capacity set of integers in [0, n).
 23type BitSet struct {
 24	n     int
 25	words []uint64
 26}
 27
 28// New returns a BitSet holding bits [0, n). n is clamped to [0, MaxBits].
 29func New(n int) *BitSet {
 30	if n < 0 {
 31		n = 0
 32	}
 33	if n > MaxBits {
 34		n = MaxBits
 35	}
 36	return &BitSet{n: n, words: make([]uint64, (n+63)/64)}
 37}
 38
 39// Cap returns the capacity in bits.
 40func (b *BitSet) Cap() int { return b.n }
 41
 42// InRange reports whether i is a valid index.
 43func (b *BitSet) InRange(i int) bool { return i >= 0 && i < b.n }
 44
 45// Set turns bit i on and reports whether i was in range.
 46func (b *BitSet) Set(i int) bool {
 47	if !b.InRange(i) {
 48		return false
 49	}
 50	b.words[i/64] |= 1 << uint(i%64)
 51	return true
 52}
 53
 54// Clear turns bit i off and reports whether i was in range.
 55func (b *BitSet) Clear(i int) bool {
 56	if !b.InRange(i) {
 57		return false
 58	}
 59	b.words[i/64] &^= 1 << uint(i%64)
 60	return true
 61}
 62
 63// Flip inverts bit i and reports whether i was in range.
 64func (b *BitSet) Flip(i int) bool {
 65	if !b.InRange(i) {
 66		return false
 67	}
 68	b.words[i/64] ^= 1 << uint(i%64)
 69	return true
 70}
 71
 72// Has reports whether bit i is set. Out of range is false, never a panic:
 73// membership of something that cannot be a member is simply false.
 74func (b *BitSet) Has(i int) bool {
 75	if !b.InRange(i) {
 76		return false
 77	}
 78	return b.words[i/64]&(1<<uint(i%64)) != 0
 79}
 80
 81// Count returns the number of set bits (popcount).
 82func (b *BitSet) Count() int {
 83	total := 0
 84	for _, w := range b.words {
 85		total += popcount(w)
 86	}
 87	return total
 88}
 89
 90// popcount counts set bits with the classic SWAR trick — no math/bits on gno.
 91func popcount(x uint64) int {
 92	n := 0
 93	for x != 0 {
 94		x &= x - 1 // clear the lowest set bit
 95		n++
 96	}
 97	return n
 98}
 99
100// Slice returns the set bits in ascending order.
101func (b *BitSet) Slice() []int {
102	out := []int{}
103	for i := 0; i < b.n; i++ {
104		if b.Has(i) {
105			out = append(out, i)
106		}
107	}
108	return out
109}
110
111// Clone returns an independent copy.
112func (b *BitSet) Clone() *BitSet {
113	c := New(b.n)
114	copy(c.words, b.words)
115	return c
116}
117
118// sameCap reports whether two sets can be combined word-wise.
119func sameCap(a, b *BitSet) bool { return a != nil && b != nil && a.n == b.n }
120
121// Union returns a ∪ b, or nil when the capacities differ. Mismatched capacities
122// are a caller error rather than something to silently pad.
123func Union(a, b *BitSet) *BitSet { return combine(a, b, "or") }
124
125// Intersect returns a ∩ b, or nil when the capacities differ.
126func Intersect(a, b *BitSet) *BitSet { return combine(a, b, "and") }
127
128// Difference returns a \ b, or nil when the capacities differ.
129func Difference(a, b *BitSet) *BitSet { return combine(a, b, "andnot") }
130
131// SymmetricDifference returns a △ b, or nil when the capacities differ.
132func SymmetricDifference(a, b *BitSet) *BitSet { return combine(a, b, "xor") }
133
134func combine(a, b *BitSet, op string) *BitSet {
135	if !sameCap(a, b) {
136		return nil
137	}
138	out := New(a.n)
139	for i := range a.words {
140		switch op {
141		case "or":
142			out.words[i] = a.words[i] | b.words[i]
143		case "and":
144			out.words[i] = a.words[i] & b.words[i]
145		case "andnot":
146			out.words[i] = a.words[i] &^ b.words[i]
147		case "xor":
148			out.words[i] = a.words[i] ^ b.words[i]
149		}
150	}
151	return out
152}
153
154// Equal reports whether two sets have the same capacity and the same bits.
155func Equal(a, b *BitSet) bool {
156	if !sameCap(a, b) {
157		return false
158	}
159	for i := range a.words {
160		if a.words[i] != b.words[i] {
161			return false
162		}
163	}
164	return true
165}
166
167// String renders the set as '0'/'1' from bit 0 upward, which reads left-to-right
168// in index order (note this is the reverse of binary notation).
169func (b *BitSet) String() string {
170	var sb strings.Builder
171	for i := 0; i < b.n; i++ {
172		if b.Has(i) {
173			sb.WriteByte('1')
174		} else {
175			sb.WriteByte('0')
176		}
177	}
178	return sb.String()
179}
180
181// FromSlice builds a BitSet of capacity n containing the given indices.
182// Out-of-range indices are ignored.
183func FromSlice(n int, idx []int) *BitSet {
184	b := New(n)
185	for _, i := range idx {
186		b.Set(i)
187	}
188	return b
189}