【问题标题】:Converting array of bit indexes to OptionSet将位索引数组转换为 OptionSet
【发布时间】:2023-04-02 23:12:01
【问题描述】:

我正在尝试编写一个辅助函数,它将位索引数组转换为符合 OptionSet 的类。

func getOptionSet<T: OptionSet>(bitIndexes: [Int64]) -> T {
    var result: Int64 = 0
    for index in bitIndexes {
        result |= 1 << index
    }
    return T(rawValue: result) // error
}

编译失败:

Cannot invoke initializer for type 'T' with an argument list of type '(rawValue: Int64)'

我也尝试过使用 RawValue:

func getOptionSet<T: OptionSet>(bitIndexes: [T.RawValue]) {
    var result = T.RawValue()  // error

这也不行:

Cannot invoke value of type 'T.RawValue.Type' with argument list '()'

这可以吗?我需要在 T 上添加额外的约束吗?

我知道可以重写此函数以使用具体类型,但如果可能,我希望保持其通用性。

【问题讨论】:

    标签: swift generics bit-fields swift3 bitvector


    【解决方案1】:

    您的代码中的问题是 Int64T.RawValue 是 不相关,可以是不同的类型。

    但是每个无符号整数类型都可以从和转换 到UIntMax,所以问题可以通过将RawValue限制为UnsignedInteger来解决。

    使用@OOPer 的想法来定义一个自定义初始化器,这将是:

    extension OptionSet where RawValue: UnsignedInteger {
        init(bitIndexes: [Int]) {
            var result: UIntMax = 0
            for index in bitIndexes {
                result |= 1 << UIntMax(index)
            }
            self.init(rawValue: RawValue(result))
        }
    }
    

    也可以写成

    extension OptionSet where RawValue: UnsignedInteger {
        init(bitIndexes: [Int]) {
            let result = bitIndexes.reduce(UIntMax(0)) {
                $0 | 1 << UIntMax($1)
            }
            self.init(rawValue: RawValue(result))
        }
    }
    

    到目前为止我看到的所有选项集类型都有一个无符号整数 类型为原始值,但请注意,同样适用于 SignedIntegerIntMax

    例子:

    struct TestSet: OptionSet {
        let rawValue: UInt16
        init(rawValue: UInt16) {
            self.rawValue = rawValue
        }
    }
    
    let ts = TestSet(bitIndexes: [1, 4])
    print(ts) // TestSet(rawValue: 18)
    

    还可以比较 How do you enumerate OptionSetType in Swift? 的反向任务。


    更新:Swift 4 开始,UnsignedInteger 协议有一个

    public static func << <RHS>(lhs: Self, rhs: RHS) -> Self where RHS : BinaryInteger
    

    方法,这样上面的代码就可以简化为

    extension OptionSet where RawValue: UnsignedInteger {
        init(bitIndexes: [Int]) {
            self.init(rawValue: bitIndexes.reduce(0) { $0 | 1 << $1 })
        }
    }
    

    无需中间转换为“最大”整数类型。

    【讨论】:

    • 谢谢,这很有用。是否值得将所有内容都转换为 32 位平台的 UIntMax 或者这不是问题?让结果: UIntMax = bitIndexes.reduce(UIntMax(0)) { UIntMax($0) | UIntMax(1)
    • @Zmey:OptionSet 类型即使在 32 位平台上也可以有 64 位 RawValue。如果中间值仅为 32 位,那么您会丢失位。但是您不需要UIntMax($0)UIntMax(1),这些类型是自动推断的。 – 实际上我在两者之间犯了错误,正如你从编辑历史中看到的那样。现在应该是正确的。
    • @Zmey:中间结果/累加器也可以具有 RawValue 类型。问题是左移运算符&lt;&lt; 未在 (Un)SignedInteger 协议中定义,因此您必须为所有可能的原始类型定义额外的协议扩展,这是我试图避免的。 - 在这个答案的第 1 版中,我通过乘法而不是左移解决了这个问题。所以这是可能的,但我发现这段代码更优雅。
    • @Zmey:在 Swift 4 中事情变得更简单了,请参阅更新的答案。
    【解决方案2】:

    您可能需要更多设置才能使您的getOptionSet 正常工作:

    protocol OptionBitShiftable: IntegerLiteralConvertible {
        func << (lhs: Self, rhs: Self) -> Self
        func |= (lhs: inout Self, rhs: Self)
    }
    extension Int64: OptionBitShiftable {}
    extension UInt64: OptionBitShiftable {}
    //...
    

    有了以上这些,你可以这样写你的getOptionSet

    func getOptionSet<T: OptionSet where T.RawValue: OptionBitShiftable>(bitIndexes: [T.RawValue]) -> T {
        var result: T.RawValue = 0
        for index in bitIndexes {
            result |= 1 << index
        }
        return T(rawValue: result)
    }
    

    用法:

    struct MyOptionSet: OptionSet {
        var rawValue: Int64
        init(rawValue: Int64) {
            self.rawValue = rawValue
        }
    }
    let myOption: MyOptionSet = getOptionSet(bitIndexes: [1,2,3,5,7])
    print(myOption.rawValue) //->174(=2+4+8+32+128)
    

    或者,你可以像这样定义一个初始化器:

    extension OptionSet where RawValue: OptionBitShiftable {
        init(bitIndexes: [RawValue]) {
            var result: RawValue = 0
            for index in bitIndexes {
                result |= 1 << index
            }
            self.init(rawValue: result)
        }
    }
    

    你可以用作:

    let alsoMyOption = MyOptionSet(bitIndexes: [4, 6])
    print(alsoMyOption.rawValue) //->80(=16+64)
    

    【讨论】:

    • 谢谢,这很好用! @martin-r 加法对于从 Int-s 数组转换也非常有用。
    • 遗憾的是我不能同时接受这两个答案,我标记了 Martin 的答案,因为它不需要定义额外的协议,我在应用程序中使用了它。但我非常感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    相关资源
    最近更新 更多