Golang program to implement a bitset


A BitSet is a data structure that represents a fixed-size set of binary values, where each value can be either 0 or 1. It is commonly used for efficiently storing and manipulating large sets of boolean values. In this article, we will implement a bitset in go, using two different methods, the first one involves the use of slice of booleans as well as the second one involves using bit manipulation with unsigned integers. Implementation here means that we are going to perform various operations like setting, clearing and testing of individual bits within a bitset data structure.

Explanation

A BitSet is a data structure used to efficiently store and manipulate a set of binary values, where each value can be either 0 or 1. It's particularly useful for scenarios where you need to keep track of a large number of boolean values, such as representing membership or presence of elements in a set.

Bitset: [0, 1, 0, 1, 0, 0, 1, 0]
Index:   0  1  2  3  4  5  6  7

Syntax

type BitSet struct{ data []uint64; size int }

The Syntax BitSet struct represents a BitSet implemented using a slice of uint64. The data field is a slice of uint64 that stores the actual bits of the BitSet. The size field indicates the total number of bits in the BitSet. This method is memory-efficient for handling a large number of bits, and bitwise operations are performed on the uint64 elements to manipulate individual bits effectively.

Algorithm

  • Create a BitSet struct with an empty data slice of uint64 and set the size to the desired number of bits in the BitSet.

  • To set a bit at a specific position pos, calculate the index of the uint64 element in the data slice using pos / 64, and the bit's position within that uint64 element using pos % 64. Use bitwise OR (|) to set the bit at the calculated position in the uint64 element.

  • To clear a bit at a specific position pos, calculate the index of the uint64 element and the bit's position within that element, similar to the set operation. Use bitwise AND (&) with the negation of the bit mask to clear the bit at the calculated position.

  • To toggle a bit at a specific position pos, calculate the index of the uint64 element and the bit's position within that element, similar to the set operation. Use bitwise XOR (^) with the bit mask to toggle the bit at the calculated position.

  • Union, Intersection, and Difference: To perform set operations like union, intersection, and difference on two BitSets, apply the corresponding bitwise operations (OR, AND, and XOR) between the elements of the data slices of both BitSets.

  • To perform shifting and rotating operations on the BitSet, use bitwise shift and rotate operations on the data slice elements to move the bits left or right.

Example 1

In this example, we will implement a BitSet in go using a slice of uint64. Each uint64 element in the slice can represent 64 bits, allowing us to efficiently handle a large number of bits. The NewBitSet function initializes the BitSet with the specified size. The Set function sets a particular bit at the given index, the Clear function clears a bit at the given index, and the Test function checks if a bit is set at the given index.

package main

import "fmt"

type BitSet struct {
	bitArray []uint64
	size     int
}

func NewBitSet(size int) *BitSet {
	sliceSize := (size + 63) / 64
	return &BitSet{
		bitArray: make([]uint64, sliceSize),
		size:     size,
	}
}

func (bs *BitSet) Set(bit int) {
	if bit < 0 || bit >= bs.size {
		return
	}

	index := bit / 64
	offset := bit % 64
	bs.bitArray[index] |= 1 << offset
}

func (bs *BitSet) Clear(bit int) {
	if bit < 0 || bit >= bs.size {
		return
	}

	index := bit / 64
	offset := bit % 64
	bs.bitArray[index] &= ^(1 << offset)
}

func (bs *BitSet) Test(bit int) bool {
	if bit < 0 || bit >= bs.size {
		return false
	}

	index := bit / 64
	offset := bit % 64
	return (bs.bitArray[index] & (1 << offset)) != 0
}

func main() {
	bitSet := NewBitSet(128)

	bitSet.Set(1)
	bitSet.Set(3)
	bitSet.Set(5)
	bitSet.Set(120)

	fmt.Println("Bit at position 1 is set:", bitSet.Test(1))   
	fmt.Println("Bit at position 10 is set:", bitSet.Test(10)) 
	fmt.Println("Bit at position 3 is set:", bitSet.Test(3))   

}

Output

Bit at position 1 is set: true
Bit at position 10 is set: false
Bit at position 3 is set: true

Example 2

In this example, we will implement a BitSet in go using a fixed-size array of integers. Here each integer represents a fixed number of bits. For example, using a 32-bit integer, we can represent 32 bits. We will demonstrate how to set, clear, and test individual bits in the BitSet. We'll use a 32-bit integer array to represent the BitSet, where each element can hold 32 bits.

package main

import (
	"fmt"
)

type BitSet struct {
	bitArray []uint32
	size     int
}

func NewBitSet(size int) *BitSet {
	numElements := (size + 31) / 32
	return &BitSet{
		bitArray: make([]uint32, numElements),
		size:     size,
	}
}

func (bs *BitSet) Set(bit int) {
	if bit < 0 || bit >= bs.size {
		return
	}

	element := bit / 32
	bitWithinElement := bit % 32
	bs.bitArray[element] |= (1 << uint32(bitWithinElement))
}

func (bs *BitSet) Clear(bit int) {
	if bit < 0 || bit >= bs.size {
		return
	}

	element := bit / 32
	bitWithinElement := bit % 32
	bs.bitArray[element] &= ^(1 << uint32(bitWithinElement))
}

func (bs *BitSet) Test(bit int) bool {
	if bit < 0 || bit >= bs.size {
		return false
	}

	element := bit / 32
	bitWithinElement := bit % 32
	return bs.bitArray[element]&(1<<uint32(bitWithinElement)) != 0
}

func main() {
	bitSet := NewBitSet(128)

	bitSet.Set(1)
	bitSet.Set(3)
	bitSet.Set(5)
	bitSet.Set(120)

	fmt.Println("Bit at position 1 is set:", bitSet.Test(1))   
	fmt.Println("Bit at position 10 is set:", bitSet.Test(10)) 
	fmt.Println("Bit at position 3 is set:", bitSet.Test(3))   

}

Output

Bit at position 1 is set: true
Bit at position 10 is set: false
Bit at position 3 is set: true

Real life implementations

Network Packet Filtering

BitSets are used in networking for packet filtering, where routers and firewalls match incoming packets against rules. Each rule is represented as a BitSet with bits indicating conditions like source IP range, protocol, or data flags. Efficient bitwise operations help quickly decide whether a packet is allowed.

Sparse Data Indexing

In databases and search engines, BitSet indexing is used for sparse data. It compresses attributes into bits, saving memory. For instance, in a search engine, BitSet identifies documents containing specific terms. Set bits represent term presence, making indexing and querying faster.

Conclusion

A BitSet is a data structure used to efficiently store and manipulate a set of binary values, where each value can be either 0 or 1. In this article we have looked at how to implement a BitSet in Go: one using a slice of uint64 and the other using a fixed-size array of integers. The first example is memory-efficient for handling a large number of bits, while the second example is more memory-efficient, especially for a smaller number of bits.

Updated on: 05-Sep-2023

113 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements