Skip to content

Instantly share code, notes, and snippets.

@SintraWorks
Created May 1, 2023 10:14
Show Gist options
  • Save SintraWorks/40a52d5f299d10040e73b2ad4dcc1355 to your computer and use it in GitHub Desktop.
Save SintraWorks/40a52d5f299d10040e73b2ad4dcc1355 to your computer and use it in GitHub Desktop.
A BitField provides storage for a set a flags representing bits
import Foundation
/// A `BitField` is a struct that provides storage for a set a flags,
/// where each flag represents a bit either being set or not set (clear).
///
/// `BitField` is closely related to Swift's `OptionSet`, but it specializes
/// on treating bits as bits, rather than as abstract flags.
public struct BitField<T: UnsignedInteger> {
/// `naturalSize` represents the maximum number of bits that a `BitField` can hold, depending on the
/// size of the type over which it is generic.
///
/// A `BitField<UInt8>` has a `naturalSize` of `8`.
///
/// A `BitField<UInt16>` has a `naturalSize` of `16`.
///
/// A `BitField<UInt32>` has a `naturalSize` of `32`.
///
/// A `BitField<UInt>` has a `naturalSize` of `32` or `64` depending on (hardware) platform.
///
/// A `BitField<UInt64>` has a `naturalSize` of `64`.
///
public static var naturalSize: Int { MemoryLayout<T>.size * 8 }
/// A BitField's `size` represents the number of bits in the BitField.
/// Setting a size larger than the `naturalSize` is not allowed.
///
/// If you try to set a size larger than the `naturalSize`, the `size` will be the `naturalSize`.
public var size: Int = Self.naturalSize {
didSet {
if size > Self.naturalSize {
size = Self.naturalSize
}
}
}
/// The `rawValue` is the numeric value that represents the sum of the values of the bits that are set.
public var rawValue: T = 0
/// The default initializer creates an empty `BitField` if no `rawValue` is provided.
/// It also defaults the size of the` BitField` to its natural size.
public init(rawValue: T = 0) {
size = Self.naturalSize
self.rawValue = rawValue
}
/// You can instantiate a `BitField` by optionally providing a raw value, and a size
/// that is smaller than, or equal to, its natural size.
/// The rawValue that is passed in must not be greater than the amount the bits can represent.
public init?(rawValue: T = 0, size: Int) {
guard size > 0, size <= Self.naturalSize,
rawValue < Int(pow(Double(2), Double(size)))
else { return nil }
self.size = size
self.rawValue = rawValue
}
/// You can instantiate a `BitField` by providing a variadic set of indexes, and, optionally, a size
/// that is smaller than, or equal to, its natural size.
/// An index represents a bit in the `BitField`, with index 0 representing the least significant bit.
/// The initializer will fail if one or more invalid indexes are supplied.
public init?(_ indexes: T..., size: Int = Self.naturalSize) {
guard setInitialIndexes(indexes, size: size)
else { return nil }
}
/// You can instantiate a `BitField` by providing an array of indexes, and, optionally, a size
/// that is smaller than, or equal to, its natural size.
/// An index represents a bit in the `BitField`, with index 0 representing the least significant bit.
/// The initializer will fail if one or more invalid indexes are supplied.
public init?(_ indexes: [T], size: Int = Self.naturalSize) {
guard setInitialIndexes(indexes, size: size)
else { return nil }
}
private mutating func setInitialIndexes(_ indexes: [T], size: Int) -> Bool {
guard size > 0, size <= Self.naturalSize
else { return false }
self.size = size
for index in indexes {
guard index < size else { return false }
rawValue |= (1 << index)
}
return true
}
/// Set one or more bits in the `BitField`.
/// Invalid indexes are ignored.
@discardableResult
public mutating func set(_ indexes: T...) -> Self {
for index in indexes {
guard index < size else { continue }
rawValue |= (1 << index)
}
return self
}
/// Clear one or more bits in the `BitField`.
/// Invalid indexes are ignored.
@discardableResult
public mutating func clear(_ indexes: T...) -> Self {
for index in indexes {
guard index < size else { continue }
rawValue = rawValue & ~(1 << index)
}
return self
}
/// Check whether one or more bits in the `BitField` are set.
/// If all bits passed in are set this function returns `true`.
/// If at least one of the checked bits is not set, or if any invalid bit indexes
/// are provided, this function returns `false`.
public func isSet(_ indexes: T...) -> Bool {
for index in indexes {
guard index < size else { return false }
if (rawValue & (1 << index)) == 0 {
return false
}
}
return true
}
func debugDescription() -> String {
(0 ..< size).reversed().reduce("0b") {
$0 + (isSet(T($1)) ? "1" : "0")
}
}
}
extension BitField {
/// Initialize a `BitField` from an array of `bool`s.
public init(_ bools: [Bool]) {
rawValue = 0
for (index, bool) in bools.enumerated() where bool {
set(T(index))
}
}
/// Turn a `BitField` into an array of `bool`s.
public func boolArrayOf(size: Int) -> [Bool] {
let validSize = min(size, Self.naturalSize)
var array = [Bool]()
for bitIndex in 0 ..< validSize {
array.append(isSet(T(bitIndex)))
}
return array
}
/// Create a copy of the `BitField` with the sequence of bits reversed.
public func reversed(size: Int? = nil) -> BitField {
BitField(boolArrayOf(size: size ?? Self.naturalSize).reversed())
}
}
extension BitField: Comparable {
public static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
extension BitField {
public func contains(_ other: BitField) -> Bool {
self & other == other
}
public static func & (lhs: Self, rhs: Self) -> Self {
BitField(rawValue: lhs.rawValue & rhs.rawValue)
}
public static func | (lhs: Self, rhs: Self) -> Self {
BitField(rawValue: lhs.rawValue | rhs.rawValue)
}
public static func ^ (lhs: Self, rhs: Self) -> Self {
BitField(rawValue: lhs.rawValue ^ rhs.rawValue)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment