【问题标题】:How do I convert a bitmask Int into a set of Ints?如何将位掩码 Int 转换为一组 Int?
【发布时间】:2016-11-30 19:08:16
【问题描述】:

我想要一个接收位掩码Int 的函数,并将其掩码值作为一组Ints 返回。像这样的:

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

这样

split(bitmask: 0b01001110) == [0b1000000, 0b1000, 0b100, 0b10]

【问题讨论】:

  • 你的意思是0b01001110?十六进制值 0x01001110 与二进制数 0b01001110 具有非常不同的位集。
  • @rmaddy 是的,谢谢!我想这是肌肉记忆:)

标签: swift bitmask


【解决方案1】:

一种解决方案是检查每个位,如果设置了相应的掩码,则添加相应的掩码。

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))

对于二进制数0b01001110,结果将是:

[64, 2, 4, 8]

这是您问题中结果的十进制等值。

对于十六进制数 0x01001110(二进制为 1000100010000),结果将是:

[16、256、4096、16777216]

这是另一个不需要知道值大小的解决方案,它对于较小的数字稍微更有效:

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
}

【讨论】:

  • @MartinR 我使用&amp;* 修复了可能的溢出问题。
【解决方案2】:

请注意,位掩码最常见的用例包括将一组特定的、有意义的布尔标志打包成单个字大小的值,并对这些标志执行测试。 Swift 在OptionSet 类型中为此提供了便利。

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)

(当然,在实际使用中,您会为 OptionSet 中的每个“元素”值输入一些对您的用例有意义的值。)

OptionSet 实际上是一个单一的值(它支持集合代数本身,而不是元素类型),所以它不是一个集合——也就是说,它不提供枚举的方法它的元素。但是如果您打算使用位掩码的方式只需要设置和测试特定标志(或标志组合),那么您可能不需要枚举元素的方法。

如果您确实需要枚举元素,但还需要 OptionSet 的所有集合代数特征,您可以将 OptionSet 与位分割数学结合起来,例如在 @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]

【讨论】:

  • 谢谢,但我应该提到,对于这个特定的用例,OptionSet 不是一个选项:c
【解决方案3】:

这是我的“1 行”版本:

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

效率不高,但很有趣!

编辑:包装在拆分函数中...

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 })
}

编辑:短一点

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-30
    相关资源
    最近更新 更多