BITOP:执行二进制位运算

用户可以通过 BITOP 命令,对一个或多个位图执行指定的二进制位运算,并将运算结果存储到指定的键中:

BITOP operation result_key bitmap [bitmap ...]

operation 参数的值可以是 AND、OR、XOR、NOT 中的任意一个,这 4 个值分别对应逻辑并、逻辑或、逻辑异或和逻辑非 4 种运算,其中 AND、OR、XOR 这 3 种运算允许用户使用任意数量的位图作为输入,而 NOT 运算只允许使用一个位图作为输入。BITOP 命令在将计算结果存储到指定键中之后,会返回被存储位图的字节长度。

举个例子,通过执行以下命令,我们可以对位图 bitmap001、bitmap002 和 bitmap003 执行逻辑并运算,并将结果存储到键 and_result 中:

redis> BITOP AND and_result bitmap001 bitmap002 bitmap003
(integer) 3 -- 运算结果and_result位图的长度为3字节

而执行以下命令则可以将 bitmap001、bitmap002 和 bitmap003 的逻辑或运算结果和逻辑异或运算结果分别存储到指定的键中:

redis> BITOP OR or_result bitmap001 bitmap002 bitmap003
(integer) 3
redis> BITOP XOR xor_result bitmap001 bitmap002 bitmap003
(integer) 3

最后,通过执行以下命令,我们可以计算出 bitmap001 的逻辑非结果,并将它存储到 not_result 键中:

redis> BITOP NOT not_result bitmap001
(integer) 1

处理不同长度的位图

当 BITOP 命令在对两个长度不同的位图执行运算时,会将长度较短的那个位图中不存在的二进制位的值看作 0。

比如,当用户使用 BITOP 命令对值为 1001110001011101 的位图和值为 10100001 的位图执行逻辑并运算时,命令将把较短的后一个位图看作 0000000010100001,然后再执行运算。

其他信息

  • 复杂度:O(N),其中 N 为计算涉及的字节总数量。

  • 版本要求:BITOP 命令从 Redis 2.6.0 版本开始可用。

示例:0-1矩阵

0-1 矩阵(又称逻辑矩阵或者二进制矩阵)是由 0 和 1 组成的矩阵,这种矩阵通常用于表示离散结构。图8-11展示了一个 0-1 矩阵的例子。

image 2025 01 03 22 27 56 736
Figure 1. 图8-11 一个0-1矩阵

Redis 的位图也可以用于存储 0-1 矩阵,只要我们把 0-1 矩阵中的各个元素与位图中的各个二进制位一对一地关联起来就可以了。图8-12 就展示了如何将图8-11 所示的 0-1 矩阵存储到一个包含 16 个二进制位的位图上面,这 16 个二进制位被划分成了 4 个部分,每个部分存储着矩阵中的一个行。

image 2025 01 03 22 28 49 126
Figure 2. 图8-12 使用位图存储0-1矩阵

代码清单8-2 展示了一个使用以上原理实现的 0-1 矩阵存储程序:

  • 在初始化矩阵对象时,程序会要求用户输入矩阵的行数和列数,然后把这两个值分别存储到对象的 row_num 属性和 col_num 属性中。

  • 当用户调用 set(row,col,value) 方法对矩阵第 row 行第 col 列的元素进行设置的时候,程序会根据公式 row*col_num+col 找出被设置元素在位图中对应的二进制位偏移量,然后执行 SETBIT 命令对该二进制位进行设置。

  • 与此类似,当用户调用 get(row,col) 方法尝试获取矩阵在指定位置上的元素时,程序会使用相同的公式,找出指定元素在位图中对应的二进制位,然后返回它的值。

代码清单8-2 0-1矩阵存储程序:/bitmap/zero_one_matrix.py
def make_matrix_key(matrix_name):
    return "matrix::" + matrix_name

def calculate_index(row, col, row_num, col_num):
    if not (row < row_num):
        raise ValueError("row out of range")
    if not (col < col_num):
        raise ValueError("col out of range")
    return row*col_num+col

class ZeroOneMatrix:

    def __init__(self, client, name, row_num, col_num):
        self.client = client
        self.bitmap = make_matrix_key(name)
        self.row_num = row_num
        self.col_num = col_num

    def set(self, row, col, value):
        """
        对矩阵的指定位置进行设置。
        """
        index = calculate_index(row, col, self.row_num, self.col_num)
        self.client.setbit(self.bitmap, index, value)

    def get(self, row, col):
        """
        获取矩阵在指定位置上的值。
        """
        index = calculate_index(row, col, self.row_num, self.col_num)
        return self.client.getbit(self.bitmap, index)

    def show(self):
        """
        打印出整个矩阵。
        """
        for row in range(self.row_num):
            elements = []
            for col in range(self.col_num):
                elements.append(self.get(row, col))
            print("matrix[{0}]: {1}".format(row, elements))

以下代码演示了这个矩阵程序的使用方法:

>>> from redis import Redis
>>> from zero_one_matrix import ZeroOneMatrix
>>> client = Redis()
>>> matrix = ZeroOneMatrix(client, "test-matrix", 4, 4)
>>> matrix.set(0, 0, 1) # 设置矩阵元素
>>> matrix.set(0, 1, 1)
>>> matrix.set(0, 2, 1)
>>> matrix.set(0, 3, 1)
>>> matrix.set(1, 1, 1)
>>> matrix.set(1, 3, 1)
>>> matrix.set(2, 2, 1)
>>> matrix.set(3, 3, 1)
>>> matrix.get(0, 0) # 获取矩阵元素
1
>>> matrix.get(1, 0)
0
>>> matrix.show() # 打印整个矩阵
matrix[0]: [1, 1, 1, 1]
matrix[1]: [0, 1, 0, 1]
matrix[2]: [0, 0, 1, 0]
matrix[3]: [0, 0, 0, 1]