Ziglings 笔记 98: 位运算实战 - 全字母句判断
26 个开关
在 Ziglings 的第 98 个练习中,我们面对的是一个经典的算法题:Pangram (全字母句)。 全字母句是指包含字母表中所有字母(a-z)至少一次的句子。例如:“The quick brown fox jumps over the lazy dog.”。
通常的解法是开一个 bool seen[26] 数组。但作为系统程序员,我们追求极致的效率。Zig 向我们展示了如何用位运算来优雅地解决这个问题。
挑战:压缩状态
我们需要遍历字符串,记录每个字母是否出现过。 最后,检查是否所有 26 个字母都出现了。
解决方案
我们使用一个 u32 整数,将其看作是 32 个开关的集合。我们只需要用到低 26 个开关。
const std = @import("std");
const ascii = std.ascii;
const print = std.debug.print;
pub fn main() !void {
print("Is this a pangram? {}!\n", .{isPangram("The quick brown fox jumps over the lazy dog.")});
}
fn isPangram(str: []const u8) bool {
// 优化:如果长度小于 26,绝对不可能是全字母句
if (str.len < 26) return false;
// 状态容器:32位整数,初始化为全 0
var bits: u32 = 0;
for (str) |c| {
// 过滤:只处理字母
if (ascii.isAscii(c) and ascii.isAlphabetic(c)) {
// 核心逻辑:计算偏移量
// 'a' -> 0, 'b' -> 1 ... 'z' -> 25
const index = ascii.toLower(c) - 'a';
// 设置位:使用位移和按位或
// 1 << index 生成掩码,|= 将对应位置 1
bits |= @as(u32, 1) << @truncate(index);
}
}
// 验证:是否所有 26 个位都为 1?
// 2^26 - 1 = 0x3FFFFFF
return bits == 0x3FFFFFF;
}
核心知识点总结
1. 空间效率
- Bool 数组方案:26 字节(甚至更多,取决于对齐)。
- 位掩码方案:4 字节(u32)。 在 CPU 缓存极其宝贵的今天,数据越紧凑,缓存命中率越高,程序跑得越快。
2. 位操作的原子性
虽然本题没有涉及并发,但位操作的一个巨大优势是它通常对应单条 CPU 指令。在底层驱动开发中,寄存器的每一位都控制着硬件的不同功能,这种 |= (Set) 和 &= ~ (Clear) 的操作是基本功。
3. 十六进制计算技巧
如何快速写出 0x3FFFFFF?
记住 F 就是 1111 (4 个 bit)。
26 个 bit = 6 个 F (24 bit) + 2 个 bit。
剩下的 2 个 bit 是 11,即 3。
组合起来就是 3 后面跟 6 个 F。
后续预告:我们已经通过位运算深入了数据的微观世界。接下来的练习可能会带我们回到宏观世界,探讨 Zig 的 Errors (错误处理) 进阶用法,或者深入标准库的 Sorting (排序) 功能。