Как преобразовать битовую маску Int в набор Int?

Мне нужна функция, которая принимает битовую маску Int и возвращает ее замаскированные значения в виде набора Int. Что-то вроде этого:

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

такой, что

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

person Ky Leggiero    schedule 30.11.2016    source источник
comment
Вы имеете в виду 0b01001110? Шестнадцатеричное значение 0x01001110 имеет очень разные наборы битов, чем двоичное число 0b01001110.   -  person rmaddy    schedule 30.11.2016
comment
@rmaddy да, спасибо! Наверное, это мышечная память :)   -  person Ky Leggiero    schedule 30.11.2016


Ответы (3)


Одним из решений является проверка каждого бита и добавление соответствующей маски, если бит установлен.

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
}
person rmaddy    schedule 30.11.2016
comment
@MartinR Я исправил возможное переполнение, используя &*. - person rmaddy; 01.12.2016

Обратите внимание, что наиболее распространенные варианты использования битовых масок включают упаковку набора определенных значимых логических флагов в одно значение размером в слово и выполнение тестов для этих флагов. 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:

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]
person rickster    schedule 30.11.2016
comment
Спасибо, но я должен был упомянуть, что для этого конкретного случая использования OptionSet не подходит :c - person Ky Leggiero; 30.11.2016

Вот моя "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 })
person TomSwift    schedule 30.11.2016