Zig语言编程示例

部分代码非本人编写。
以下部分Zig代码可以在AttemptThisOnline运行。

推荐些阅读材料

Zig by Example
官方文档
Zig中文教程,非官方

Zig语言简介

Zig 是一种命令式、通用、静态类型、编译的系统编程语言,由 Andrew Kelley 设计。 它旨在替代C编程语言,其目标是更小,编程更简单,同时还提供现代功能,新的优化和各种安全机制, 同时不像其他语言那样对运行时安全性的要求。

语言简单性的改进与流控制、函数调用、库导入、变量声明和 Unicode 支持有关。 此外,该语言不使用宏或预处理器指令。从现代语言中采用的功能包括添加编译时泛型类型, 允许函数处理各种数据,以及一小组新的编译器指令,以允许使用反射访问有关这些类型的信息。 Zig 还旨在提高代码的安全性,它不提供垃圾回收(GC),但是使用可选类型代替 null ,这避免了空指针的出现。

在线执行Zig的平台

有很多

以下列出了,可以在线执行 zig 的平台:

zig-playground 版本:info: Zig version: 0.12.0-dev.126+fe424bd4b
zig-play 版本:info: Zig version: 0.13.0
ATO上的zig版本是:info: Zig version: 0.13.0

debug-print

const std = @import("std");
std.debug.print("z={},d={},n={},k={}\n", .{z, d, n, k});

函数返回类型

看起来pub fn main() !void 要比pub fn main() void好用。
这是联合错误类型,该语法将会告诉 zig 编译器会返回错误或者值,此处的意思是如果有返回值,一定是一个错误。

pub fn main() !void {
    const result = try generateSequence(5, 13);
    for (result) |item| {
        std.debug.print("{}, ", .{item});
    }
    std.debug.print("\n", .{});
}

allocator有哪些

std.heap.page_allocator

const allocator = std.heap.page_allocator;

const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);

try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);
const byte = try std.heap.page_allocator.create(u8);
defer std.heap.page_allocator.destroy(byte);
byte.* = 128;

std.heap.FixedBufferAllocator

var buffer: [1000]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();

const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);

try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);

std.heap.GeneralPurposeAllocator


var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
        const deinit_status = gpa.deinit();
        //fail test; can't try in defer as defer is executed after we return
        if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL");
}

const bytes = try allocator.alloc(u8, 100);
defer allocator.free(bytes);

for-loop和while-loop

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {
    var array = [_]u32{ 1, 2, 3 };

    for (array) |elem| {
        print("by val: {}\n", .{elem});
    }

    for (&array) |*elem| {
        elem.* += 100;
        print("by ref: {}\n", .{elem.*});
    }

    for (array, &array) |val, *ref| {
        _ = val;
        _ = ref;
    }

    for(0..4) |my_index| {
       print("index: {}\n", .{my_index});
    }

    for (0.., array) |i, elem| {
        print("{}: {}\n", .{ i, elem });
    }

    for (array) |_| {}

    var a: usize = 0;
    var b: usize = 0;
    var c: usize = 0;
    var d: usize = 0;

    while (a < 2) {
        print("a: {}\n", .{a});
        a += 1;
    }

    while (b < 2) : (b += 1) {
        print("b: {}\n", .{b});
    }

    while (c < 4) : ({
        c += 1;
        c += 1;
    }) {
        print("c: {}\n", .{c});
    }

    while (true) {
        break;
    }

    while (true) : (d += 1) {
        if (d < 2) {
            print("d: {}\n", .{d});
            continue;
        }

        break;
    }
}

for enumerate

0.. 表示半无限长的序列,拿它和原序列做zip

const std = @import("std");

pub fn main() !void {
    const a = [_]i32{ 10, 20, 30, 40, 50 };
    
    // Method 1: Using array indices
    for (a, 0..) |ele, index| {
        std.debug.print("Index: {}, Element: {}\n", .{ index, ele });
    }
}

ArrayList

const std = @import("std");
const expect = std.testing.expect;

const eql = std.mem.eql;
const ArrayList = std.ArrayList;
const test_allocator = std.heap.page_allocator;

pub fn main() !void {
    var list = ArrayList(u8).init(test_allocator);
    defer list.deinit();
    try list.append('H');
    try list.append('e');
    try list.append('l');
    try list.append('l');
    try list.append('o');
    try list.appendSlice(" World!");

    try expect(eql(u8, list.items, "Hello World!"));
}

Arrays

const std = @import("std");
const print = std.debug.print;

pub fn main() !void {

    const a = [3]i32{ 1, 2, 3 };

    const b = [_]i32{ 4, 5, 6 };

    const c: [3]i32 = .{ 7, 8, 9 };

    var d: [3]i32 = undefined;
    d[0] = 10;
    d[1] = 11;
    d[2] = 12;

    print("len: {}\n", .{c.len});

    print("repeat: {any}\n", .{a ** 2});

    print("concat: {any}\n", .{a ++ b});

    for (d) |elem| {
        print("elem: {}\n", .{elem});
    }



    //var array1:  [8]u8       = .{ 1,2,3,4,5,6,7,8 };
    //var array2d: [2][4]u8    = .{ .{1,2,3,4}, .{5,6,7,8} };
    //var array3d: [2][2][2]u8 = .{ .{ .{1,2}, .{3,4} }, .{ .{5,6}, .{7,8} } };
    const mat4x4 = [4][4]f32{
       [_]f32{ 1.0, 0.0, 0.0, 0.0 },
       [_]f32{ 0.0, 1.0, 0.0, 1.0 },
       [_]f32{ 0.0, 0.0, 1.0, 0.0 },
       [_]f32{ 0.0, 0.0, 0.0, 1.0 },
    };

    for (mat4x4) |row| {
       for(row) |elem| {
          print("elem: {}\n", .{elem});
       }
    }
}

字符的ASCII码

const std = @import("std");

pub fn main() !void {
    const N: usize = 4;
    const perms = [_][]const u8{
        "ABCD", "CABD", "ACDB", "DACB", "BCDA", "ACBD", "ADCB", "CDAB",
        "DABC", "BCAD", "CADB", "CDBA", "CBAD", "ABDC", "ADBC", "BDCA",
        "DCBA", "BACD", "BADC", "BDAC", "CBDA", "DBCA", "DCAB",
    };

    // Calculate n = (N-1)!, the expected number of occurrences
    var n: usize = 1;
    var i: usize = 1;
    while (i < N) : (i += 1) {
        n *= i;
    }

    var miss: [N]u8 = undefined;
    var stdout = std.io.getStdOut().writer();

    i = 0;
    while (i < N) : (i += 1) {
        var cnt = [_]usize{0} ** N;
        
        // Count how many times each letter occurs at position i
        for (perms) |perm| {
            const position = perm[i] - 'A';
            cnt[position] += 1;
        }

        // Find letter not occurring (N-1)! times - that's the missing one
        var j: usize = 0;
        while (j < N and cnt[j] == n) : (j += 1) {}

        miss[i] = @as(u8, @intCast(j)) + 'A';
    }

    try stdout.print("Missing: {s}\n", .{miss});
}

为什么zig 0.14.0中不能使用std.mem.copy

error: root source file struct 'mem' has no member named 'copy'

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const a = [_]i32{ 9, 8, 7, 6, 5, 0, 1, 2, 3, 4 };
    
    var n: usize = 0;
    while (n < a.len) : (n += 1) {
        var b = try allocator.alloc(i32, a.len);
        defer allocator.free(b);
        
        std.mem.copy(i32, b, &a);
        nthElement(i32, b, n);
        
        const stdout = std.io.getStdOut().writer();
        try stdout.print("n = {}, nth element = {}\n", .{ n + 1, b[n] });
    }
}

改成逐个元素赋值的形式:

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const a = [_]i32{ 9, 8, 7, 6, 5, 0, 1, 2, 3, 4 };
    
    var n: usize = 0;
    while (n < a.len) : (n += 1) {
        var b = try allocator.alloc(i32, a.len);
        defer allocator.free(b);
        
        // Copy elements one by one instead of using mem.copy
        for (0..a.len) |i| {
            b[i] = a[i];
        }
        
        nthElement(i32, b, n);
        
        const stdout = std.io.getStdOut().writer();
        try stdout.print("n = {}, nth element = {}\n", .{ n + 1, b[n] });
    }
}

zig 0.14.1中std.fs.openFileAbsolute读取文件/proc/stat并处理,显示Linux CPU utilization

Linux CPU utilization

const std = @import("std");
const print = std.debug.print;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;

const CpuUtilization = struct {
    idle: f64,
    not_idle: f64,
};

const ParseError = error{
    InvalidFormat,
    ZeroTotal,
    ParseIntError,
};

const AppError = ParseError || std.fs.File.OpenError || std.fs.File.ReadError || std.mem.Allocator.Error;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    run(allocator) catch |err| {
        print("Error: {}\n", .{err});
        return;
    };
}

fn run(allocator: Allocator) !void {
    const proc_stat_line = try procStat(allocator);
    defer allocator.free(proc_stat_line);
    
    const percentages = try parseUtilization(proc_stat_line);
    
    print("{s:<10} {d:.2}%\n", .{ "idle", percentages.idle * 100.0 });
    print("{s:<10} {d:.2}%\n", .{ "not-idle", percentages.not_idle * 100.0 });
}

fn procStat(allocator: Allocator) ![]u8 {
    const file = try std.fs.openFileAbsolute("/proc/stat", .{});
    defer file.close();
    
    const data = try file.readToEndAlloc(allocator, 4096);
    defer allocator.free(data);
    
    // Find the first line (up to newline)
    var lines = std.mem.splitSequence(u8, data, "\n");
    const first_line = lines.next() orelse "";
    
    return try allocator.dupe(u8, first_line);
}

/// Parse the /proc/stat line to extract CPU utilization percentages
/// 
/// Arguments:
/// * `line` - The first line from /proc/stat
/// 
/// Returns:
/// A struct containing idle and not_idle percentages
fn parseUtilization(line: []const u8) !CpuUtilization {
    // Remove "cpu " prefix and trim whitespace
    const values_str = if (std.mem.startsWith(u8, line, "cpu "))
        std.mem.trimLeft(u8, line[4..], " ")
    else
        std.mem.trimLeft(u8, line, " ");
    
    var total: u64 = 0;
    var idle: u64 = 0;
    
    var iter = std.mem.splitSequence(u8, values_str, " ");
    var index: usize = 0;
    
    while (iter.next()) |value_str| {
        if (value_str.len == 0) continue;
        
        const num = std.fmt.parseInt(u64, value_str, 10) catch |err| switch (err) {
            error.InvalidCharacter, error.Overflow => return ParseError.ParseIntError,
        };
        
        if (index == 3) {
            idle = num;
        }
        total += num;
        index += 1;
    }
    
    if (total == 0) {
        return ParseError.ZeroTotal;
    }
    
    const idle_percentage = @as(f64, @floatFromInt(idle)) / @as(f64, @floatFromInt(total));
    const not_idle_percentage = 1.0 - idle_percentage;
    
    return CpuUtilization{
        .idle = idle_percentage,
        .not_idle = not_idle_percentage,
    };
}

zig 0.14.0中split函数被废弃了

zig的docusaurus文档建议这么写

//pub const split = @compileError("deprecated; use splitSequence, splitAny, or splitScalar");
pub fn solve(self: *NSolver, puzz: []const u8, max_wid: usize) ![][]const u8 {
	// Split the input string into tokens
	var tokens = std.ArrayList([]const u8).init(self.allocator);
	defer tokens.deinit();
	
	var iter = std.mem.splitSequence(u8, puzz, " ");
	while (iter.next()) |tok| {
		if (tok.len == 0) continue; // Skip empty tokens
		try tokens.append(tok);
	}
	
	if (tokens.items.len == 0) return &[_][]const u8{};

	self.wid = max_wid;
	self.hei = tokens.items.len / max_wid;
	self.max = tokens.items.len;
	const len = self.wid * self.hei;

	// allocate and zero‐initialize the board
	self.arr = try self.allocator.alloc(Node, len);
	for (self.arr) |*n| {
		n.* = Node{ .val = 0, .neighbors = 0 };
	}

	// parse the input tokens into our board
	var c: usize = 0;
	for (tokens.items) |tok| {
		if (std.mem.eql(u8, tok, "*")) {
			self.max -= 1;
			self.arr[c].val = -1;
		} else if (std.mem.eql(u8, tok, ".")) {
			// leave . as 0 for "empty"
			self.arr[c].val = 0;
		} else {
			// parse a pre‐filled number
			const v = try std.fmt.parseInt(isize, tok, 10);
			self.arr[c].val = v;
		}
		c += 1;
	}

	// run the backtracking search
	self.solveIt();

	// Create result array to hold the modified tokens
	var result = try self.allocator.alloc([]const u8, tokens.items.len);
	
	// write back any "." slots with zero‐padded numbers
	c = 0;
	for (tokens.items, 0..) |tok, i| {
		if (std.mem.eql(u8, tok, ".")) {
			const v = self.arr[c].val;
			// format as two digits, zero-pad
			result[i] = try std.fmt.allocPrint(self.allocator, "{d:0>2}", .{v});
		} else {
			// For non-modified tokens, just copy the reference
			result[i] = tok;
		}
		c += 1;
	}
	
	return result;
}

实战——变长二维数组

下面这份Zig代码我写了一个多小时。

const std = @import("std");
const allocator = std.heap.page_allocator;

pub fn main() !void {
    const result = try generateSequence(5, 13);
    for (result) |item| {
        std.debug.print("{}", .{item});
    }
    std.debug.print("\n", .{});
}

fn generateSequence(_k: i32, _n: i32) ![]i32 {
    var k=_k; var n=_n;

    var s = std.ArrayList(std.ArrayList(i32)).init(allocator);

    for (0..@as(usize, @intCast(n))) |i| {
        var innerList = std.ArrayList(i32).init(allocator);
        if (i < k) {
            try innerList.append(1);
        } else {
            try innerList.append(0);
        }
        try s.append(innerList);
    }

    var d:i32 = n-k;  
    n = @max(k, d);
    k = @min(k, d);
    var z = d;

    while (z > 0 or k > 1) {
        for (0..@as(usize, @intCast(k))) |i| {
            var lastList = s.items[s.items.len - 1 - i];
            for (lastList.items) |item| {
                try s.items[i].append(item);
            }
        }
        s.shrinkRetainingCapacity(s.items.len - @as(usize, @intCast(k)));
        z -= k;
        d = n - k;
        n = @max(k, d);
        k = @min(k, d);
    }

    var result = std.ArrayList(i32).init(allocator);

    for (s.items) |sublist| {
        for (sublist.items) |item| {
            try result.append(item);
        }
    }
    return result.toOwnedSlice();
}

实战——算一下组合数

const std = @import("std");

/// Compute the integer power base^exp, with exp ≥ 0.
/// If exp < 0 in your use case, you’ll need custom handling for negative exponents.
fn powInt(base: i128, exp: i128) i128 {
    var result: i128 = 1;
    var i: i128 = 0;
    while (i < exp) : (i += 1) {
        result *= base;
    }
    return result;
}

/// Compute the binomial coefficient C(n, k) = n! / (k! * (n-k)!).
/// Returns 0 if n or k are negative or if k > n.
fn comb(n: i32, k: i32) i128 {
    if (k < 0 or n < 0 or k > n) {
        return 0;
    }
    var result: i128 = 1;
    var numerator: i128 = @as(i128, n);
    var denominator: i128 = 1;

    // Only compute up to k_capped = min(k, n-k)
    const k_capped = if (k < (n - k)) k else (n - k);

    var i: i32 = 0;
    while (i < k_capped) : (i += 1) {
        result = @divTrunc( (result * numerator) , denominator);   // @divExact @divFloor  @divTrunc

        numerator -= 1;
        denominator += 1;
    }

    return result;
}

/// Translated function f from Python:
/// f = lambda n,v,x,y,z: C(n,v)*C(n-v,x-v)*C(n-x,y-v)*C(n-x-y+v,z-v)*4^(n-x-y-z+2*v)
fn f(n: i32, v: i32, x: i32, y: i32, z: i32) i128 {
    const part1 = comb(n, v);
    const part2 = comb(n - v, x - v);
    const part3 = comb(n - x, y - v);
    const part4 = comb(n - x - y + v, z - v);

    // exponent = (n - x - y - z + 2*v)
    const exponent = @as(i128, n - x - y - z + (2 * v));
    // If exponent < 0, it effectively yields 0 for integer purposes, but here we keep it simple:
    if (exponent < 0) return 0;

    const part5 = powInt(4, exponent);

    return part1 * part2 * part3 * part4 * part5;
}

pub fn main() !void {
    // A small inline "assert_eq" for i128 comparisons
    const assert_eq = struct {
        pub fn check(actual: i128, expected: i128) void {
            if (actual != expected) {
                std.debug.print(
                    "Assertion failed! Expected {}, got {}.\n",
                    .{ expected, actual }
                );
                @panic("assertion failed");
            }
        }
    };

    // Tests matching the Rust and Python version
    assert_eq.check(f(2, 0, 1, 1, 1), 0);
    assert_eq.check(f(2, 1, 1, 1, 1), 8);
    assert_eq.check(f(2, 2, 1, 1, 1), 0);
    assert_eq.check(f(8, 0, 2, 3, 3), 560);
    assert_eq.check(f(8, 1, 2, 3, 3), 80640);
    assert_eq.check(f(8, 2, 2, 3, 3), 215040);
    assert_eq.check(f(8, 3, 2, 3, 3), 0);

    std.debug.print("All tests passed!\n", .{});
}

实战——考虑溢出的整数加法

uint32_t temp = std::rotl(a, 5) + f + e + k + values[j];
e = d; d = c; c = std::rotl(b, 30); b = a; a = temp;
// Use wrapping add to prevent integer overflow panics
const rotated_a = std.math.rotl(u32, a, 5);
const temp = @addWithOverflow(rotated_a, f)[0];
const temp2 = @addWithOverflow(temp, e)[0];
const temp3 = @addWithOverflow(temp2, k)[0];
const temp4 = @addWithOverflow(temp3, values[j])[0];
                
e = d;
d = c;
c = std.math.rotl(u32, b, 30);
b = a;
a = temp4;

实战——Zig 0.14.0中使用随机数

Cipolla's algorithm, Solve x² ≡ n (mod p)

const std = @import("std");
const math = std.math;

// Random number generator in range [min, max]
fn rangeRandom(min: u64, max: u64) u64 {
    if (min > max) return min;
    // seed it however you like, here we just use the timestamp:
    var prng = std.Random.DefaultPrng.init(@as(u64, @intCast(std.time.milliTimestamp())));
    // directly pull a number in [min, max]
    return prng.random().intRangeAtMost(u64, min, max);
}

实战——使用Zig实现一些图论算法

Dijkstra's algorithm

Hamiltonian Cycle via Chvátal Closure

There is no std.BTreeSet in Zig's standard library

You should replace std.BTreeSet with std.ArrayList since BTreeSet doesn't exist in Zig's standard library

posted @ 2024-02-05 14:28  yhm138  阅读(347)  评论(0)    收藏  举报