从位运算看海象运算符

前言

突然发现了Python3.8版本引入的海象运算符,就借着位运算的需求来看看是怎么个用法。

需求

需求是这样的,我有一张表,表中存在很多类东西,每个类都有一个自增编号。为了让自增编号转变为one-hot编码,需要将自增的编号转换为二进制。

当然,确实是可以直接使用Python的二进制转换工具,但那样就完全没意思了不是吗?

先找出需要多少位数

首先,我们需要知道,自增编号的最大值是多少。

既然是表里面存储的,那么这一点就比较好办:

1
SELECT MAX(id) FROM table_name;

不管你用jdbc也好,ODBC也罢,反正你拿到了这个最大值。于是你开始准备着手计算:

1
2
3
4
5
def get_max_length(max_id):
count = 1
while max_id := max_id >> 1:
count += 1
return count

这就是海象运算符的用法。在判断的时候,先计算海象运算符右侧,然后把结果赋值给左侧,最后判断左值是否满足条件。

使用限制

当然,这并不是Go语言,所以海象运算符并不是哪里都能用。

赋值场景

在赋值过程中,如果搭配括号,那么海象运算符与赋值符号是完全一致的。

1
a = (a - 30) / 2

这个看着相当简单。而如果我说可以用海象运算符装个莫名其妙的逼呢?

1
a = (a := (a := a - 30) / 2)

看起来很诡异,但是原理上与传统的赋值符号用法完全一致。毕竟二者都是一个顺序:

先计算右值、再赋给左值。

当然,二者也不是完全不同。海象运算符不可以直接赋值

传统赋值过程中,如果我们是可以直接指定的:

1
a = 30

海象运算符不一样,会报错:

1
2
a := 30
~~^^~~~

这就是为什么,我们在装逼的时候,最左边的赋值还只能是=符号。

判断场景

海象运算符最大的特点就是,本身返回的是值,所以判断场景才是海象运算符最常用的场景。就比如说,我需要移位,传统过程只能这么做:

1
2
3
a = a >> 1
if a > 0:
print(a)

因为=没有返回值,只能将右值给到左值。

海象运算符不一样,在将右值给到左值的基础上,还能够把左值返回出去:

1
2
if a := a >> 1:
print(a)

不过需要注意的是,海象运算符的优先级比较低,在真实场景使用的时候需要带上括号:

1
2
3
if a := 30 > 40:
print(a) # 不输出
print(a) # 输出False,因为优先级算了30 > 40

转为one-hot编码

当然,由于是one-hot编码,我们并不能直接使用二进制数值,因为这样做无法转为矩阵。

不过好在,我们还有海象运算符,我们可以这么做:

1
2
3
4
5
6
7
8
9
10
11
def transfer2b(num):
# 先把最初的一位数加进去
onehot = [num & 1]
# 海象运算符循环处理
while num := num >> 1:
# 使用切片的方式增加数据
onehot[:0] = [num & 1]
# 由于并非极端场景,所以与`insert`差距不大
# 下面是`insert`写法:
# onehot.insert(0, num & 1)
return onehot

那么我们在使用的时候就会很方便:

1
print(transfer2b(64)) # 输出:[1, 0, 0, 0, 0, 0, 0]

一件小事情

最开始的时候,我始终认为是只需要位二进制,后来才想起来,用二进制表示的时候还有一个需要表示。

所以,如果是,其实需要位,因为位只能够表示。数字虽然确实是有个,但由于从开始,所以实际最大也就少了一个。