You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用NumPy高效生成所有n位格雷码?

如何用NumPy高效生成所有n位格雷码?

我太懂你这种被库版本依赖折腾到崩溃的感受了——装了一堆库,每个都要抢不同版本的NumPy,更新一个就崩一片,干脆沉下心啃NumPy本身反而更踏实!你想要的是纯NumPy实现、最高效生成n位格雷码,输出形状为np.zeros((1 << n, n), dtype=bool)的布尔数组对吧?结合你自己做的大量测试和实现,我整理出最靠谱的高效方案,还有关键的性能优化思路。


先明确核心约束

  • 纯NumPy实现,禁用其他任何库
  • 输出必须是(2^n, n)的布尔数组
  • 追求极致性能,优先内存连续、低复制的操作

高效实现方案

方案1:从二进制码快速转换(基于np.unpackbits)

你自己测试发现np.unpackbits是生成二进制码最快的方式之一,我们可以基于这个快速生成二进制码,再转成格雷码——格雷码的核心规则是:最高位与二进制码相同,其余位为二进制当前位与前一位的异或。

先把你写的工具函数和核心实现整合:

import numpy as np

# 工具函数:获取对应位宽的无符号整数类型
UINT_BITS = {}
lo, hi = 1, 8
for dtype in (np.uint8, np.uint16, np.uint32, np.uint64):
    for i in range(lo, hi + 1):
        UINT_BITS[i] = dtype
    lo, hi = hi + 1, hi << 1

def get_dtype(n: int) -> np.dtype:
    if dtype := UINT_BITS.get(n):
        return dtype
    raise ValueError(f"Argument {n} is not a valid bit width")

def validate(n: int) -> None:
    if not (isinstance(n, int) and n > 0):
        raise ValueError(f"Argument {n} is not a valid positive integer bit width")

# 最快的二进制码生成(你测试的binary_codes_6)
def binary_codes_fast(n: int) -> np.ndarray:
    validate(n)
    return np.unpackbits(
        np.arange(1 << n, dtype=get_dtype(n))[:, None].view(np.uint8),
        axis=1,
        bitorder="little",
        count=n,
    )[:, ::-1].astype(bool)

# 基于二进制转格雷码的高效实现
def gray_codes_from_binary(n: int) -> np.ndarray:
    binary = binary_codes_fast(n)
    gray = binary.copy()
    # 从第二位开始,当前位 = 二进制当前位 ^ 二进制前一位
    gray[:, 1:] = binary[:, 1:] ^ binary[:, :-1]
    return gray

方案2:直接生成格雷码(递归拼接法)

格雷码的经典生成规则是:n位格雷码可以由n-1位格雷码拼接得到——前半部分补0,后半部分倒序补1。这个方法避免了二进制转码的步骤,纯拼接操作:

def gray_codes_direct(n: int) -> np.ndarray:
    validate(n)
    # 初始化1位格雷码
    gray = np.array([[0], [1]], dtype=bool)
    current_length = 2
    for _ in range(n - 1):
        # 前半部分:补0 + 原格雷码
        part1 = np.concatenate([np.zeros((current_length, 1), dtype=bool), gray], axis=1)
        # 后半部分:补1 + 倒序的原格雷码
        part2 = np.concatenate([np.ones((current_length, 1), dtype=bool), gray[::-1]], axis=1)
        # 拼接得到新的格雷码
        gray = np.concatenate([part1, part2], axis=0)
        current_length <<= 1
    return gray

方案3:数值异或后拆分(你实现的gray_codes_0/1优化版)

先生成0到2^n-1的整数,通过num ^ (num >> 1)得到格雷码数值,再拆分成布尔数组。这个方法利用NumPy的位运算,性能也很出色:

def gray_codes_bitwise(n: int) -> np.ndarray:
    validate(n)
    count = 1 << n
    # 生成所有数值,转格雷码
    nums = np.arange(count, dtype=get_dtype(n))
    gray_nums = nums ^ (nums >> 1)
    # 拆分每一位为布尔数组(循环赋值,内存更连续)
    result = np.zeros((count, n), dtype=bool)
    mask = count >> 1
    for i in range(n):
        result[:, i] = gray_nums & mask
        mask >>= 1
    return result

性能对比(基于你做的测试,n=16)

实现方法耗时(均值±标准差)核心优势
binary_codes_fast1.12ms ± 9.71μs利用np.unpackbits硬件级加速
gray_codes_from_binary~1.5ms(估算)基于最快二进制生成,异或操作轻量
gray_codes_bitwise2.12ms ±25.1μs纯位运算,内存访问连续
gray_codes_direct~1.4ms(估算)无转码步骤,纯拼接操作
你的gray_codes_24.51ms ±151μs依赖roll操作,有额外复制开销

你测试出的关键性能优化点(必须记牢!)

这些都是你踩坑试出来的NumPy性能真经:

  • 优先用np.concatenate替代hstack/vstack/dstack:尤其是指定axis参数的concatenate,底层更少的形状检查和内存复制,比如np.concatenate([a,b], axis=-1)比np.hstack([a,b])快10%以上;
  • 广播操作要注意数组形状:(a & b[:, None]).T比(a[:, None] & b)快近30%,因为前者的内存访问更符合CPU缓存的连续读取规则;
  • 循环赋值列有时比广播高效:比如binary_codes_5比binary_codes_4快,因为逐列赋值避免了大数组的广播开销,内存更紧凑;
  • np.unpackbits是二进制生成的天花板:只要位宽不超过64,用unpackbits结合view转换类型,比任何手动拼接/广播都快,记得用bitorder="little"和count=n过滤多余位。

备注:内容来源于stack exchange,提问作者Ξένη Γήινος

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 18:53:06