第8章: 配列と文字列

学習目標

この章を終えると、以下ができるようになります:

  • 配列の基本操作を理解する
  • 文字列とUTF-8エンコーディングを扱える
  • 文字列処理ユーティリティを実装できる
  • 効率的な配列操作ができる
  • 配列の基本

    配列の宣言と初期化

    const std = @import("std");
    
    pub fn example() void {
        // サイズ指定の配列
        const arr1: [5]i32 = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 型推論
        const arr2 = [_]i32{ 10, 20, 30 };
    
        // すべて同じ値で初期化
        const arr3 = [_]i32{42} ** 5; // [42, 42, 42, 42, 42]
    
        // 未初期化配列
        var arr4: [10]i32 = undefined;
        arr4[0] = 1;
    
        std.debug.print("arr1: ", .{});
        for (arr1) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    
        std.debug.print("arr3: ", .{});
        for (arr3) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    配列のサイズ

    const std = @import("std");
    
    pub fn example() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // 配列のサイズを取得
        std.debug.print("Length: {}\n", .{numbers.len});
        std.debug.print("Size in bytes: {}\n", .{@sizeOf(@TypeOf(numbers))});
    }
    

    多次元配列

    const std = @import("std");
    
    pub fn example() void {
        // 2次元配列
        const matrix = [_][3]i32{
            [_]i32{ 1, 2, 3 },
            [_]i32{ 4, 5, 6 },
            [_]i32{ 7, 8, 9 },
        };
    
        std.debug.print("Matrix:\n", .{});
        for (matrix) |row| {
            for (row) |value| {
                std.debug.print("{} ", .{value});
            }
            std.debug.print("\n", .{});
        }
    }
    

    配列操作

    配列の反復

    const std = @import("std");
    
    pub fn example() void {
        const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
        // インデックスなし
        for (numbers) |num| {
            std.debug.print("{} ", .{num});
        }
        std.debug.print("\n", .{});
    
        // インデックス付き
        for (numbers, 0..) |num, i| {
            std.debug.print("[{}]={} ", .{i, num});
        }
        std.debug.print("\n", .{});
    }
    

    配列のコピー

    const std = @import("std");
    
    pub fn example() void {
        const src = [_]i32{ 1, 2, 3, 4, 5 };
        var dest: [5]i32 = undefined;
    
        // 要素ごとにコピー
        for (src, 0..) |value, i| {
            dest[i] = value;
        }
    
        // または std.mem.copy を使用
        @memcpy(&dest, &src);
    
        std.debug.print("Dest: ", .{});
        for (dest) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    配列の比較

    const std = @import("std");
    
    pub fn example() void {
        const arr1 = [_]i32{ 1, 2, 3 };
        const arr2 = [_]i32{ 1, 2, 3 };
        const arr3 = [_]i32{ 1, 2, 4 };
    
        // 配列の等価性チェック
        const eq1 = std.mem.eql(i32, &arr1, &arr2);
        const eq2 = std.mem.eql(i32, &arr1, &arr3);
    
        std.debug.print("arr1 == arr2: {}\n", .{eq1});
        std.debug.print("arr1 == arr3: {}\n", .{eq2});
    }
    

    配列のソート

    const std = @import("std");
    
    pub fn example() void {
        var numbers = [_]i32{ 5, 2, 8, 1, 9, 3 };
    
        // 昇順ソート
        std.mem.sort(i32, &numbers, {}, comptime std.sort.asc(i32));
    
        std.debug.print("Sorted: ", .{});
        for (numbers) |n| std.debug.print("{} ", .{n});
        std.debug.print("\n", .{});
    }
    

    文字列の基本

    文字列リテラル

    const std = @import("std");
    
    pub fn example() void {
        // 文字列は []const u8 または [*:0]const u8
        const str1: []const u8 = "Hello, Zig!";
        const str2: [*:0]const u8 = "Null-terminated";
    
        std.debug.print("str1: {s}\n", .{str1});
        std.debug.print("str2: {s}\n", .{str2});
    
        // 文字列の長さ
        std.debug.print("Length: {}\n", .{str1.len});
    }
    

    文字列の連結

    const std = @import("std");
    
    pub fn example() !void {
        const allocator = std.heap.page_allocator;
    
        const str1 = "Hello";
        const str2 = " World";
    
        // std.fmt.allocPrint を使用
        const result = try std.fmt.allocPrint(allocator, "{s}{s}", .{str1, str2});
        defer allocator.free(result);
    
        std.debug.print("Concatenated: {s}\n", .{result});
    }
    

    文字列の比較

    const std = @import("std");
    
    pub fn example() void {
        const str1 = "abc";
        const str2 = "abc";
        const str3 = "xyz";
    
        // 等価性チェック
        const eq1 = std.mem.eql(u8, str1, str2);
        const eq2 = std.mem.eql(u8, str1, str3);
    
        std.debug.print("str1 == str2: {}\n", .{eq1});
        std.debug.print("str1 == str3: {}\n", .{eq2});
    
        // 辞書順比較
        const cmp = std.mem.order(u8, str1, str3);
        std.debug.print("str1 vs str3: {}\n", .{cmp});
    }
    

    UTF-8エンコーディング

    UTF-8の基本

    const std = @import("std");
    
    pub fn example() void {
        const str = "こんにちは世界";
    
        // バイト長
        std.debug.print("Byte length: {}\n", .{str.len});
    
        // 文字数を数える
        var count: usize = 0;
        var i: usize = 0;
        while (i < str.len) {
            const byte_len = std.unicode.utf8ByteSequenceLength(str[i]) catch break;
            i += byte_len;
            count += 1;
        }
        std.debug.print("Character count: {}\n", .{count});
    }
    

    UTF-8の反復

    const std = @import("std");
    
    pub fn example() !void {
        const str = "Hello 世界 🌍";
    
        var iter = (try std.unicode.Utf8View.init(str)).iterator();
    
        std.debug.print("Characters:\n", .{});
        while (iter.nextCodepoint()) |codepoint| {
            std.debug.print("U+{X:0>4} ", .{codepoint});
        }
        std.debug.print("\n", .{});
    }
    

    文字の種類判定

    const std = @import("std");
    
    pub fn example() void {
        const chars = "Aa1 @";
    
        for (chars) |ch| {
            std.debug.print("'{c}': ", .{ch});
            std.debug.print("alpha={}, ", .{std.ascii.isAlphabetic(ch)});
            std.debug.print("digit={}, ", .{std.ascii.isDigit(ch)});
            std.debug.print("space={}\n", .{std.ascii.isWhitespace(ch)});
        }
    }
    

    文字列操作

    部分文字列

    const std = @import("std");
    
    pub fn example() void {
        const str = "Hello, World!";
    
        // スライスで部分文字列を取得
        const substr1 = str[0..5];   // "Hello"
        const substr2 = str[7..12];  // "World"
    
        std.debug.print("substr1: {s}\n", .{substr1});
        std.debug.print("substr2: {s}\n", .{substr2});
    }
    

    文字列の検索

    const std = @import("std");
    
    pub fn example() void {
        const str = "Hello, World!";
    
        // 文字の検索
        if (std.mem.indexOf(u8, str, "World")) |index| {
            std.debug.print("Found 'World' at index {}\n", .{index});
        }
    
        // 最後から検索
        if (std.mem.lastIndexOf(u8, str, "o")) |index| {
            std.debug.print("Last 'o' at index {}\n", .{index});
        }
    }
    

    文字列の分割

    const std = @import("std");
    
    pub fn example() void {
        const str = "apple,banana,cherry";
    
        var iter = std.mem.split(u8, str, ",");
    
        std.debug.print("Split:\n", .{});
        while (iter.next()) |token| {
            std.debug.print("  {s}\n", .{token});
        }
    }
    

    文字列のトリム

    const std = @import("std");
    
    pub fn example() void {
        const str = "  Hello, World!  ";
    
        // 空白を削除
        const trimmed = std.mem.trim(u8, str, " ");
    
        std.debug.print("Original: '{s}'\n", .{str});
        std.debug.print("Trimmed: '{s}'\n", .{trimmed});
    }
    

    文字列フォーマット

    基本的なフォーマット

    const std = @import("std");
    
    pub fn example() !void {
        const allocator = std.heap.page_allocator;
    
        // 整数のフォーマット
        const str1 = try std.fmt.allocPrint(allocator, "Number: {}", .{42});
        defer allocator.free(str1);
    
        // 浮動小数点のフォーマット
        const str2 = try std.fmt.allocPrint(allocator, "Pi: {d:.2}", .{3.14159});
        defer allocator.free(str2);
    
        // 複数の値
        const str3 = try std.fmt.allocPrint(allocator, "x={}, y={}", .{10, 20});
        defer allocator.free(str3);
    
        std.debug.print("{s}\n", .{str1});
        std.debug.print("{s}\n", .{str2});
        std.debug.print("{s}\n", .{str3});
    }
    

    カスタムフォーマット

    const std = @import("std");
    
    const Point = struct {
        x: i32,
        y: i32,
    
        pub fn format(
            self: Point,
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            writer: anytype,
        ) !void {
            _ = fmt;
            _ = options;
            try writer.print("({}, {})", .{self.x, self.y});
        }
    };
    
    pub fn example() void {
        const point = Point{ .x = 10, .y = 20 };
        std.debug.print("Point: {}\n", .{point});
    }
    

    実践例

    例1: 文字列ユーティリティ

    const std = @import("std");
    
    const StringUtils = struct {
        pub fn toUpper(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
            const result = try allocator.alloc(u8, str.len);
            for (str, 0..) |ch, i| {
                result[i] = std.ascii.toUpper(ch);
            }
            return result;
        }
    
        pub fn toLower(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
            const result = try allocator.alloc(u8, str.len);
            for (str, 0..) |ch, i| {
                result[i] = std.ascii.toLower(ch);
            }
            return result;
        }
    
        pub fn reverse(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
            const result = try allocator.alloc(u8, str.len);
            for (str, 0..) |ch, i| {
                result[str.len - 1 - i] = ch;
            }
            return result;
        }
    };
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
        const str = "Hello, World!";
    
        const upper = try StringUtils.toUpper(allocator, str);
        defer allocator.free(upper);
    
        const lower = try StringUtils.toLower(allocator, str);
        defer allocator.free(lower);
    
        const reversed = try StringUtils.reverse(allocator, str);
        defer allocator.free(reversed);
    
        std.debug.print("Original: {s}\n", .{str});
        std.debug.print("Upper: {s}\n", .{upper});
        std.debug.print("Lower: {s}\n", .{lower});
        std.debug.print("Reversed: {s}\n", .{reversed});
    }
    

    例2: CSV パーサー

    const std = @import("std");
    
    const CsvParser = struct {
        allocator: std.mem.Allocator,
    
        pub fn init(allocator: std.mem.Allocator) CsvParser {
            return CsvParser{ .allocator = allocator };
        }
    
        pub fn parse(self: CsvParser, line: []const u8) ![][]const u8 {
            var fields = std.ArrayList([]const u8).init(self.allocator);
            defer fields.deinit();
    
            var iter = std.mem.split(u8, line, ",");
            while (iter.next()) |field| {
                const trimmed = std.mem.trim(u8, field, " ");
                try fields.append(trimmed);
            }
    
            return fields.toOwnedSlice();
        }
    };
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
        const parser = CsvParser.init(allocator);
    
        const line = "Alice, 25, Tokyo";
        const fields = try parser.parse(line);
        defer allocator.free(fields);
    
        std.debug.print("Fields:\n", .{});
        for (fields, 0..) |field, i| {
            std.debug.print("  [{}]: {s}\n", .{i, field});
        }
    }
    

    例3: テンプレートエンジン(簡易版)

    const std = @import("std");
    
    fn replaceAll(
        allocator: std.mem.Allocator,
        haystack: []const u8,
        needle: []const u8,
        replacement: []const u8,
    ) ![]u8 {
        var result = std.ArrayList(u8).init(allocator);
        defer result.deinit();
    
        var i: usize = 0;
        while (i < haystack.len) {
            if (std.mem.startsWith(u8, haystack[i..], needle)) {
                try result.appendSlice(replacement);
                i += needle.len;
            } else {
                try result.append(haystack[i]);
                i += 1;
            }
        }
    
        return result.toOwnedSlice();
    }
    
    pub fn main() !void {
        const allocator = std.heap.page_allocator;
    
        const template = "Hello, {{name}}! You are {{age}} years old.";
        var result = try replaceAll(allocator, template, "{{name}}", "Alice");
        defer allocator.free(result);
    
        const final = try replaceAll(allocator, result, "{{age}}", "25");
        defer allocator.free(final);
    
        std.debug.print("{s}\n", .{final});
    }
    

    まとめ

    この章では、Zigの配列と文字列について学びました:

  • 配列: 宣言、初期化、多次元配列、基本操作
  • 配列操作: 反復、コピー、比較、ソート
  • 文字列: リテラル、連結、比較
  • UTF-8: エンコーディング、反復、文字種判定
  • 文字列操作: 部分文字列、検索、分割、トリム
  • フォーマット: 基本的なフォーマット、カスタムフォーマット
  • これでZig Foundationsの基礎的な内容を学び終えました。次のステップでは、より高度なトピック(構造体、列挙型、エラーハンドリングなど)に進みます。

    参考資料

  • Zig Language Reference - Arrays: https://ziglang.org/documentation/master/#Arrays
  • Zig Standard Library - mem: https://ziglang.org/documentation/master/std/#std.mem
  • Zig Standard Library - unicode: https://ziglang.org/documentation/master/std/#std.unicode
  • Ziglearn - Strings: https://ziglearn.org/chapter-1/#strings