代入式を使って CRC32 を一つの lambda 式にする (Python 3.8 以降)


zlib などで使われている CRC32 は以下のような計算になっています。

Python3
def crc32(data, P=0xedb88320, v=0):
    v = v ^ 0xffffffff
    for d in data:
        v = v ^ d
        for b in range(8):
            v = (v >> 1) ^ ((v & 1) and P)
    return v ^ 0xffffffff

これを、一つの lambda 式にしようとしたとき、data に応じた変数 v の更新が必要ですが、 Python 3.7 以前では v を更新する方法を見つけられませんでした。しかし、Python 3.8 から代入式 ":=" が使えるので、それが可能です。

Python3.8
crc32 = lambda data, P=0xedb88320, v=0xffffffff: 0xffffffff ^ [v := ((v ^ (0 if b else d)) >> 1) ^ (((v ^ (0 if b else d)) & 1) and P) for d in data for b in range(8)][-1]

この式は、入力ビット数(入力バイト数の 8 倍)の配列を一度作るので、CRC32 としての実用性はゼロです。

zlib の crc32 と一致するか確認します。

test.py
#!/usr/bin/env python3.8

import zlib

def crc32a(data, P=0xedb88320, v=0):
    v ^= 0xffffffff
    for d in data:
        v ^= d
        for b in range(8):
            v = (v >> 1) ^ ((v & 1) and P)
    return v ^ 0xffffffff


crc32b = lambda data, P=0xedb88320, v=0xffffffff: 0xffffffff ^ [v := ((v ^ (0 if b else d)) >> 1) ^ (((v ^ (0 if b else d)) & 1) and P) for d in data for b in range(8)][-1]


def test(data, encoding='utf-8'):
    data = bytearray(data) if type(data) != str else bytearray(data, encoding)
    a = crc32a(data)
    b = crc32b(data)
    z = zlib.crc32(data)
    if a != z:
        raise Exception('BUG(crc32a): %#010x != %#010x' % (a, z))
    if b != z:
        raise Exception('BUG(crc32b): %#010x != %#010x' % (b, z))
    print('OK: %#010x' % z)


test('abcd')
test(b'efgh')
実行結果
$ python3.8 test.py
OK: 0xed82cd11
OK: 0x08337bb5

参考: SSE4.2 拡張命令 CRC32 を使ってみる