【问题标题】:how to decode integer bitmask to original input characters如何将整数位掩码解码为原始输入字符
【发布时间】:2019-08-08 02:37:53
【问题描述】:

我正在从将可选输入字符标志存储为打包整数(使用二进制打包)的应用程序中读取数据。为了便于讨论,请使用以下示例:A=1, B=2, C=4, D=8, E=16 :
当用户输入:'AC'时,存储的值为5 (=1+4)
当用户输入:'ABCD'时,存储的值为15 (=1+2+4+8)
我想从整数值(5 => 'AC'15 => 'ABCD')中恢复原始输入。大小写和顺序并不重要。

我是这方面的新手,所以请阅读位掩码和按位运算并编写一些工作代码。基本上,我将整数转换为格式化为 0/1 字符串的二进制值。然后我检查每个字符串位置(位)的 True/False (1/0) 。当 True 时,我将匹配字符添加到另一个字符串(来自输入选项的有序字符串)。

我的直觉告诉我有一种更简单的方法可以做到这一点;在单个操作中使用二进制表示作为检查字符串的“掩码”。我发现了一些涉及位掩码的帖子,但没有回答我的问题:
Checking specific bits of a bitmask
How to take bit number 3 from 8 bits

是否可以简化下面的代码?
它适用于 i_flag = 1 到 31 的所有值。
[我知道我需要对无效 i_flag 值进行错误检查(=0>(n_char**2)-1
一旦我设置了转换逻辑,我将添加它。]

chk_str = 'EDCBA'
i_flag = 1
str_flag=''
b_flag = ('{:0'+str(len(chk_str))+'b}').format(i_flag)
for pos in range(len(b_flag)) :
   if int(b_flag[pos]):
       str_flag += chk_str[pos]

print ('for int=', i_flag, ',flags are:',str_flag)      

【问题讨论】:

    标签: python bitmask


    【解决方案1】:

    处理这个问题的一个很酷的方法是创建一个通用的BitMask

    import string
    import itertools
    
    
    class BitMask(object):
        STR_TOKENS = string.ascii_letters
        STR_EMPTY = '_'
        STR_FULL = False
    
        def __init__(
                self,
                value=None,
                ignore=True):
            if isinstance(value, str):
                self.value = self.from_tokens(value, self.STR_TOKENS, ignore)
            else:
                self.value = value
    
        def __repr__(self):
            return bin(self.value)
    
        def __iter__(self):
            value = self.value
            while value:
                yield value & 1
                value >>= 1
    
        def to_tokens(self, tokens, empty, full):
            if full:
                return [
                    token if value else empty
                    for token, value in
                    itertools.zip_longest(tokens, self, fillvalue=False)]
            else:
                return [
                    token for token, value in zip(tokens, self) if value]
    
        def __str__(self):
            return ''.join(
                self.to_tokens(self.STR_TOKENS, self.STR_EMPTY, self.STR_FULL))
    
        def from_tokens(self, seq, tokens, ignore):
            if tokens is None:
                tokens = self.STR_TOKENS
            valid_tokens = set(tokens)
            value = 0
            for i, item in enumerate(seq):
                if item in valid_tokens:
                    value |= 1 << tokens.index(item)
                elif not ignore:
                    raise ValueError(f'Invalid input `{item}` at index: {i}.')
            return value
    
        def __add__(self, other):
            self.value |= other.value
            return self
    
        def __mul__(self, other):
            self.value &= other.value
            return self
    
        def __eq__(self, other):
            return type(self) == type(other) and self.value == other.value
    

    您可以根据需要对其进行子类化,例如:

    class MyBitMask(BitMask):
        STR_TOKENS = string.ascii_uppercase
        def __init__(self, value=None, ignore=False):
            super().__init__(value, ignore)
    
    
    print(str(MyBitMask(5)))
    # AC
    print(str(MyBitMask(15)))
    # ABCD
    

    还有:

    repr(MyBitMask('AC'))
    # 0b101
    
    MyBitMask('AC') == MyBitMask(5)
    # True
    
    MyBitMask('AC') == BitMask(5)  # NOT THE SAME BITMASK CLASS!
    # False
    

    在时间上,冷静会消耗速度,即(与其他答案中的方法相比):

    def convert(value, tokens=string.ascii_uppercase):
        output = ''
        i = 0
        while value:
            if value & 1:
                output += tokens[i]
            i += 1
            value >>= 1
        return output
    
    
    def convert2(value, tokens=string.ascii_uppercase):
        return ''.join(tokens[i] for i, c in enumerate(bin(value)[:1:-1]) if c == '1')
    
    
    def convert3(value, tokens=string.ascii_uppercase):
        result = []
        i = 0
        while value:
            if value & 1:
                result.append(tokens[i])
            i += 1
            value >>= 1
        return ''.join(result)
    
    
    def convert4(value, tokens=string.ascii_uppercase):
        return ''.join(tokens[pos] for pos in range(value.bit_length()) if value & (1 << pos))
    
    
    def convert5(value, tokens=string.ascii_uppercase):
        return ''.join(c for b, c in zip(reversed('{:b}'.format(value)), tokens) if b == '1')
    
    print([convert(i) for i in range(16)])
    print([convert2(i) for i in range(16)])
    print([convert3(i) for i in range(16)])
    print([convert4(i) for i in range(16)])
    print([convert5(i) for i in range(16)])
    print([str(MyBitMask(i)) for i in range(16)])
    # ['', 'A', 'B', 'AB', 'C', 'AC', 'BC', 'ABC', 'D', 'AD', 'BD', 'ABD', 'CD', 'ACD', 'BCD', 'ABCD']
    
    %timeit [convert(i) for i in range(1024)]
    %timeit [convert2(i) for i in range(1024)]
    %timeit [convert3(i) for i in range(1024)]
    %timeit [convert4(i) for i in range(1024)]
    %timeit [convert5(i) for i in range(1024)]
    %timeit [str(MyBitMask(i)) for i in range(1024)]
    
    1.8 ms ± 5.45 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    2.11 ms ± 83.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    2.29 ms ± 331 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    3.42 ms ± 1.03 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
    2.24 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    4.48 ms ± 151 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

    • norok2,感谢优雅的 BitMask 类并为所有建议的答案运行计时测试。都非常有帮助。我非常偶尔这样做(因此我的无知),每次运行只需转换几个变量(因此,这里或那里的毫秒无关紧要)。对我来说,越简单越好,因为我需要在下次阅读时理解代码。 :-)
    【解决方案2】:

    您可以改用while 循环来将输入整数值移动 1 位,直到它变为 0,并且对于每次迭代,如果最低位为整数为1:

    def convert(i):
        output = ''
        pos = 0
        while i:
            if i & 1:
                output += 'ABCDE'[pos]
            pos += 1
            i >>= 1
        return output
    

    这样convert(5) 返回'AC'convert(15) 返回'ABCD'

    【讨论】:

      【解决方案3】:

      将数字转换为二进制(并忽略前 2 个字符,即 '0x')和 从字母表中选择与设置的位相对应的字符

      >>> import string
      >>> n = 5
      >>> ''.join(string.ascii_uppercase[i] for i,c in enumerate(bin(n)[:1:-1]) if c=='1')
      'AC'
      >>> 
      >>> n = 15
      >>> ''.join(string.ascii_uppercase[i] for i,c in enumerate(bin(n)[:1:-1]) if c=='1')
      'ABCD'
      >>> 
      >>> n = 2
      >>> ''.join(string.ascii_uppercase[i] for i,c in enumerate(bin(n)[:1:-1]) if c=='1')
      'B'
      

      【讨论】:

      • n = 2 在我看来是错误的,应该是 bin(n)[:1:-1] 而不是 bin(n)[2:]
      • 对不起。对 5 和 15 的二进制形式的对称性质感到困惑。更新了答案
      • 有趣的解决方案。我不明白你是如何设置要检查的字符的。我在示例中使用了 ABCDE,但选项可以是任何东西(ADXZ 或 BKTY)
      • @kcw78 即string.ascii_uppercase
      • @norok2 我应该澄清我对这个解决方案的评论。我的示例将 ABCDE 作为我的标志,但它可以是任何一组字母。如果标志选项是 BDKX 怎么办?我通过在下面的答案中将tokens= 添加到输入参数来了解这是如何处理的。很好。谢谢。
      【解决方案4】:

      或者,您可以先使用 str.format 方法将输入的整数值转换为二进制字符串,然后将其反转,然后使用映射字符串 zip ,这样您就可以使用生成器表达式过滤掉那些不是1,在将剩余字符加入字符串之前:

      def convert(i):
          return ''.join(c for b, c in zip(reversed('{:b}'.format(i)), 'ABCDE') if b == '1')
      

      这样convert(5) 返回'AC'convert(15) 返回'ABCD'

      【讨论】:

        【解决方案5】:

        您可以在输入整数的位长度上迭代一个位偏移,并相应地测试每个位:

        def convert(i):
            return ''.join('ABCDE'[pos] for pos in range(i.bit_length()) if i & (1 << pos))
        

        这样convert(5) 返回'AC'convert(15) 返回'ABCD'

        【讨论】:

          猜你喜欢
          • 2018-08-16
          • 2019-08-09
          • 2016-08-12
          • 2011-09-24
          • 2016-03-01
          • 1970-01-01
          • 2021-07-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多