// Package bitset is a dense, fixed-capacity bit vector as a pure, reusable // package: a compact set of small non-negative integers with the usual // set algebra (union, intersection, difference). // // Storage is a []uint64 of ceil(n/64) words, so 1024 bits cost 16 words rather // than 1024 booleans — the reason to reach for this on chain, where every byte // is paid for. // // Capacity is fixed at construction and every operation is bounds-checked // rather than growing: an out-of-range index is a caller bug, and silently // growing would make gas unpredictable. // // A live demo of this package is at // [r/moul/x/daily/bitsetdemo](/r/moul/x/daily/bitsetdemo/v0). package bitset import "strings" // MaxBits bounds a BitSet so allocation and iteration stay predictable. const MaxBits = 1 << 16 // 65536 bits = 1024 words = 8 KiB // BitSet is a fixed-capacity set of integers in [0, n). type BitSet struct { n int words []uint64 } // New returns a BitSet holding bits [0, n). n is clamped to [0, MaxBits]. func New(n int) *BitSet { if n < 0 { n = 0 } if n > MaxBits { n = MaxBits } return &BitSet{n: n, words: make([]uint64, (n+63)/64)} } // Cap returns the capacity in bits. func (b *BitSet) Cap() int { return b.n } // InRange reports whether i is a valid index. func (b *BitSet) InRange(i int) bool { return i >= 0 && i < b.n } // Set turns bit i on and reports whether i was in range. func (b *BitSet) Set(i int) bool { if !b.InRange(i) { return false } b.words[i/64] |= 1 << uint(i%64) return true } // Clear turns bit i off and reports whether i was in range. func (b *BitSet) Clear(i int) bool { if !b.InRange(i) { return false } b.words[i/64] &^= 1 << uint(i%64) return true } // Flip inverts bit i and reports whether i was in range. func (b *BitSet) Flip(i int) bool { if !b.InRange(i) { return false } b.words[i/64] ^= 1 << uint(i%64) return true } // Has reports whether bit i is set. Out of range is false, never a panic: // membership of something that cannot be a member is simply false. func (b *BitSet) Has(i int) bool { if !b.InRange(i) { return false } return b.words[i/64]&(1<