2

I want a function that takes in a bitmask Int, and returns its masked values as a set of Ints. Something like this:

func split(bitmask: Int) -> Set<Int> {
    // Do magic
}

such that

split(bitmask: 0b01001110) == [0b1000000, 0b1000, 0b100, 0b10]
Ky -
  • 30,724
  • 51
  • 192
  • 308
  • 2
    Do you mean `0b01001110`? The hex value `0x01001110` has very different bits sets than the binary number `0b01001110`. – rmaddy Nov 30 '16 at 19:21
  • @rmaddy yes, thank you! I suppose it was muscle memory :) – Ky - Nov 30 '16 at 20:51

3 Answers3

4

One solution is to check each bit and add the corresponding mask if the bit is set.

func split(bitmask: Int) -> Set<Int> {
    var results = Set<Int>()

    // Change 31 to 63 or some other appropriate number based on how big your numbers can be        
    for shift in 0...31 {
        let mask = 1 << shift
        if bitmask & mask != 0 {
            results.insert(mask)
        }
    }

    return results
}

print(split(bitmask: 0b01001110))

For the binary number 0b01001110 the results will be:

[64, 2, 4, 8]

which are the decimal equivalent of the results in your question.

For the hex number 0x01001110 (which is 1000100010000 in binary) the results will be:

[16, 256, 4096, 16777216]

Here's another solution that doesn't need to know the size of the value and it's slightly more efficient for smaller numbers:

func split(bitmask: Int) -> Set<Int> {
    var results = Set<Int>()

    var value = bitmask
    var mask = 1
    while value > 0 {
        if value % 2 == 1 {
            results.insert(mask)
        }

        value /= 2
        mask = mask &* 2
    }

    return results
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
3

Note that the most common use cases for bit masks include packing a collection of specific, meaningful Boolean flags into a single word-sized value, and performing tests against those flags. Swift provides facilities for this in the OptionSet type.

struct Bits: OptionSet {
    let rawValue: UInt // unsigned is usually best for bitfield math
    init(rawValue: UInt) { self.rawValue = rawValue }

    static let one = Bits(rawValue: 0b1)
    static let two = Bits(rawValue: 0b10)
    static let four = Bits(rawValue: 0b100)
    static let eight = Bits(rawValue: 0b1000)
}

let someBits = Bits(rawValue: 13)
// the following all return true:
someBits.contains(.four)
someBits.isDisjoint(with: .two)
someBits == [.one, .four, .eight]
someBits == [.four, .four, .eight, .one] // set algebra: order/duplicates moot
someBits == Bits(rawValue: 0b1011)

(In real-world use, of course, you'd give each of the "element" values in your OptionSet type some value that's meaningful to your use case.)

An OptionSet is actually a single value (that supports set algebra in terms of itself, instead of in terms of an element type), so it's not a collection — that is, it doesn't provide a way to enumerate its elements. But if the way you intend to use a bitmask only requires setting and testing specific flags (or combinations of flags), maybe you don't need a way to enumerate elements.

And if you do need to enumerate elements, but also want all the set algebra features of OptionSet, you can combine OptionSet with bit-splitting math such as that found in @rmaddy's answer:

extension OptionSet where RawValue == UInt { // try being more generic?
    var discreteElements: [Self] {
        var result = [Self]()
        var bitmask = self.rawValue
        var element = RawValue(1)
        while bitmask > 0 && element < ~RawValue.allZeros {
            if bitmask & 0b1 == 1 {
                result.append(Self(rawValue: element))
            }
            bitmask >>= 1
            element <<= 1
        }
        return result
    }
}

someBits.discreteElements.map({$0.rawValue}) // => [1, 4, 8]
Community
  • 1
  • 1
rickster
  • 124,678
  • 26
  • 272
  • 326
  • Thank you, but I should've mentioned that for this particular use case, `OptionSet` isn't an option :c – Ky - Nov 30 '16 at 20:52
1

Here's my "1 line" version:

let values = Set(Array(String(0x01001110, radix: 2).characters).reversed().enumerated().map { (offset, element) -> Int in
    Int(String(element))! << offset
    }.filter { $0 != 0 })

Not super efficient, but fun!

Edit: wrapped in split function...

func split(bitmask: Int) -> Set<Int> {
    return Set(Array(String(bitmask, radix: 2).characters).reversed().enumerated().map { (offset, element) -> Int in
        Int(String(element))! << offset
        }.filter { $0 != 0 })
}

Edit: a bit shorter

let values = Set(String(0x01001110, radix: 2).utf8.reversed().enumerated().map { (offset, element) -> Int in
    Int(element-48) << offset
    }.filter { $0 != 0 })
TomSwift
  • 39,369
  • 12
  • 121
  • 149