課題8: 配列と文字列の実践

マンダトリー要件 (80点)

Part 1: 配列操作 (20点)

配列操作の基本的な関数を実装してください。

ファイル: part1/array_ops.zig

const std = @import("std");

// TODO: 配列を反転
fn reverseArray(comptime T: type, array: []T) void {
    // 実装
}

// TODO: 配列をローテート(左に n 要素シフト)
fn rotateLeft(comptime T: type, array: []T, n: usize) void {
    // 実装
}

// TODO: 2つの配列をマージ(ソート済み配列をマージしてソート済み配列を作成)
fn mergeSorted(
    comptime T: type,
    allocator: std.mem.Allocator,
    arr1: []const T,
    arr2: []const T,
) ![]T {
    // 実装
}

// TODO: 配列から重複を削除
fn removeDuplicates(
    comptime T: type,
    allocator: std.mem.Allocator,
    array: []const T,
) ![]T {
    // 実装
}

// TODO: 配列の分割
fn partition(
    comptime T: type,
    array: []T,
    predicate: *const fn(T) bool,
) struct { true_count: usize, false_count: usize } {
    // 実装
    // 述語がtrueの要素を前に、falseの要素を後ろに配置
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Reverse Array ===\n", .{});
    var arr1 = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("Before: ", .{});
    for (arr1) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});
    reverseArray(i32, &arr1);
    std.debug.print("After: ", .{});
    for (arr1) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Rotate Left ===\n", .{});
    var arr2 = [_]i32{ 1, 2, 3, 4, 5 };
    rotateLeft(i32, &arr2, 2);
    std.debug.print("Rotated: ", .{});
    for (arr2) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Merge Sorted ===\n", .{});
    const sorted1 = [_]i32{ 1, 3, 5 };
    const sorted2 = [_]i32{ 2, 4, 6 };
    const merged = try mergeSorted(i32, allocator, &sorted1, &sorted2);
    defer allocator.free(merged);
    std.debug.print("Merged: ", .{});
    for (merged) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Remove Duplicates ===\n", .{});
    const with_dups = [_]i32{ 1, 2, 2, 3, 3, 3, 4, 5, 5 };
    const unique = try removeDuplicates(i32, allocator, &with_dups);
    defer allocator.free(unique);
    std.debug.print("Unique: ", .{});
    for (unique) |n| std.debug.print("{} ", .{n});
    std.debug.print("\n", .{});
}

Part 2: 文字列基本操作 (20点)

文字列の基本的な操作を実装してください。

ファイル: part2/string_basic.zig

const std = @import("std");

// TODO: 文字列を大文字に変換
fn toUpper(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: 文字列を小文字に変換
fn toLower(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: 文字列を反転
fn reverseString(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: 文字列が回文か判定
fn isPalindrome(str: []const u8) bool {
    // 実装
}

// TODO: 文字列内の単語数をカウント
fn countWords(str: []const u8) usize {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== To Upper/Lower ===\n", .{});
    const str = "Hello, World!";
    const upper = try toUpper(allocator, str);
    defer allocator.free(upper);
    const lower = try toLower(allocator, str);
    defer allocator.free(lower);
    std.debug.print("Original: {s}\n", .{str});
    std.debug.print("Upper: {s}\n", .{upper});
    std.debug.print("Lower: {s}\n", .{lower});

    std.debug.print("\n=== Reverse String ===\n", .{});
    const reversed = try reverseString(allocator, str);
    defer allocator.free(reversed);
    std.debug.print("Reversed: {s}\n", .{reversed});

    std.debug.print("\n=== Palindrome ===\n", .{});
    std.debug.print("'racecar' is palindrome: {}\n", .{isPalindrome("racecar")});
    std.debug.print("'hello' is palindrome: {}\n", .{isPalindrome("hello")});

    std.debug.print("\n=== Count Words ===\n", .{});
    const text = "The quick brown fox jumps";
    std.debug.print("'{s}' has {} words\n", .{text, countWords(text)});
}

Part 3: 文字列検索と置換 (20点)

文字列の検索と置換を実装してください。

ファイル: part3/string_search.zig

const std = @import("std");

// TODO: 部分文字列を検索(すべての出現位置)
fn findAll(
    allocator: std.mem.Allocator,
    haystack: []const u8,
    needle: []const u8,
) ![]usize {
    // 実装
}

// TODO: 文字列を置換(すべての出現を置換)
fn replaceAll(
    allocator: std.mem.Allocator,
    haystack: []const u8,
    needle: []const u8,
    replacement: []const u8,
) ![]u8 {
    // 実装
}

// TODO: 文字列を分割
fn split(
    allocator: std.mem.Allocator,
    str: []const u8,
    delimiter: []const u8,
) ![][]const u8 {
    // 実装
}

// TODO: 文字列を結合
fn join(
    allocator: std.mem.Allocator,
    strings: []const []const u8,
    separator: []const u8,
) ![]u8 {
    // 実装
}

// TODO: 先頭と末尾の空白を削除
fn trim(str: []const u8) []const u8 {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Find All ===\n", .{});
    const text = "hello world, hello universe";
    const positions = try findAll(allocator, text, "hello");
    defer allocator.free(positions);
    std.debug.print("'hello' found at: ", .{});
    for (positions) |pos| std.debug.print("{} ", .{pos});
    std.debug.print("\n", .{});

    std.debug.print("\n=== Replace All ===\n", .{});
    const replaced = try replaceAll(allocator, text, "hello", "hi");
    defer allocator.free(replaced);
    std.debug.print("Replaced: {s}\n", .{replaced});

    std.debug.print("\n=== Split ===\n", .{});
    const csv = "apple,banana,cherry";
    const parts = try split(allocator, csv, ",");
    defer {
        for (parts) |part| allocator.free(part);
        allocator.free(parts);
    }
    std.debug.print("Split:\n", .{});
    for (parts) |part| std.debug.print("  {s}\n", .{part});

    std.debug.print("\n=== Join ===\n", .{});
    const joined = try join(allocator, parts, " | ");
    defer allocator.free(joined);
    std.debug.print("Joined: {s}\n", .{joined});

    std.debug.print("\n=== Trim ===\n", .{});
    const padded = "  hello world  ";
    const trimmed = trim(padded);
    std.debug.print("'{s}' -> '{s}'\n", .{padded, trimmed});
}

Part 4: UTF-8処理 (20点)

UTF-8エンコーディングを扱う関数を実装してください。

ファイル: part4/utf8_ops.zig

const std = @import("std");

// TODO: UTF-8文字列の文字数をカウント
fn countUtf8Chars(str: []const u8) !usize {
    // 実装
}

// TODO: UTF-8文字列からn番目の文字を取得
fn getUtf8Char(str: []const u8, n: usize) ![]const u8 {
    // 実装
}

// TODO: UTF-8文字列を反転(文字単位)
fn reverseUtf8(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: 文字列のバイト数と文字数を返す
fn stringInfo(str: []const u8) !struct { bytes: usize, chars: usize } {
    // 実装
}

// TODO: すべての文字がASCIIか判定
fn isAscii(str: []const u8) bool {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Count UTF-8 Chars ===\n", .{});
    const str1 = "Hello";
    const str2 = "こんにちは";
    const str3 = "Hello 世界 🌍";

    std.debug.print("'{s}': {} chars\n", .{str1, try countUtf8Chars(str1)});
    std.debug.print("'{s}': {} chars\n", .{str2, try countUtf8Chars(str2)});
    std.debug.print("'{s}': {} chars\n", .{str3, try countUtf8Chars(str3)});

    std.debug.print("\n=== Get UTF-8 Char ===\n", .{});
    const char = try getUtf8Char(str2, 0);
    std.debug.print("First char of '{s}': {s}\n", .{str2, char});

    std.debug.print("\n=== Reverse UTF-8 ===\n", .{});
    const reversed = try reverseUtf8(allocator, str2);
    defer allocator.free(reversed);
    std.debug.print("Reversed: {s}\n", .{reversed});

    std.debug.print("\n=== String Info ===\n", .{});
    const info = try stringInfo(str3);
    std.debug.print("'{s}': {} bytes, {} chars\n", .{str3, info.bytes, info.chars});

    std.debug.print("\n=== Is ASCII ===\n", .{});
    std.debug.print("'{s}' is ASCII: {}\n", .{str1, isAscii(str1)});
    std.debug.print("'{s}' is ASCII: {}\n", .{str2, isAscii(str2)});
}

ボーナス課題 (20点)

Bonus 1: 正規表現風パターンマッチング (10点)

簡易的なパターンマッチング(ワイルドカード)を実装してください。

ファイル: bonus1/pattern_match.zig

const std = @import("std");

// TODO: ワイルドカードパターンマッチング
// '*' は任意の文字列にマッチ
// '?' は任意の1文字にマッチ
fn wildcardMatch(pattern: []const u8, str: []const u8) bool {
    // 実装
}

// TODO: グロブパターンで複数のファイル名をフィルタ
fn filterByPattern(
    allocator: std.mem.Allocator,
    files: []const []const u8,
    pattern: []const u8,
) ![][]const u8 {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Wildcard Match ===\n", .{});
    const tests = [_]struct { pattern: []const u8, str: []const u8 }{
        .{ .pattern = "*.txt", .str = "file.txt" },
        .{ .pattern = "*.txt", .str = "file.md" },
        .{ .pattern = "test?.c", .str = "test1.c" },
        .{ .pattern = "test?.c", .str = "test12.c" },
        .{ .pattern = "*hello*", .str = "say hello world" },
    };

    for (tests) |test| {
        const matches = wildcardMatch(test.pattern, test.str);
        std.debug.print("'{s}' matches '{s}': {}\n",
            .{test.pattern, test.str, matches});
    }

    std.debug.print("\n=== Filter by Pattern ===\n", .{});
    const files = [_][]const u8{
        "main.zig",
        "test.zig",
        "readme.md",
        "util.zig",
        "config.json",
    };

    const filtered = try filterByPattern(allocator, &files, "*.zig");
    defer allocator.free(filtered);

    std.debug.print("Files matching '*.zig':\n", .{});
    for (filtered) |file| {
        std.debug.print("  {s}\n", .{file});
    }
}

Bonus 2: 文字列圧縮と展開 (10点)

Run-Length Encoding (RLE) を実装してください。

ファイル: bonus2/compression.zig

const std = @import("std");

// TODO: RLE圧縮
// 例: "aaabbbcc" -> "3a3b2c"
fn compress(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: RLE展開
// 例: "3a3b2c" -> "aaabbbcc"
fn decompress(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
    // 実装
}

// TODO: 圧縮率を計算
fn compressionRatio(original: []const u8, compressed: []const u8) f64 {
    // 実装
}

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    std.debug.print("=== Compress ===\n", .{});
    const original = "aaabbbcccdddd";
    const compressed = try compress(allocator, original);
    defer allocator.free(compressed);
    std.debug.print("Original: {s}\n", .{original});
    std.debug.print("Compressed: {s}\n", .{compressed});

    const ratio = compressionRatio(original, compressed);
    std.debug.print("Ratio: {d:.2}%\n", .{ratio * 100.0});

    std.debug.print("\n=== Decompress ===\n", .{});
    const decompressed = try decompress(allocator, compressed);
    defer allocator.free(decompressed);
    std.debug.print("Decompressed: {s}\n", .{decompressed});

    const match = std.mem.eql(u8, original, decompressed);
    std.debug.print("Match: {}\n", .{match});
}

評価基準

項目 配点
Part 1: 配列操作 20点
Part 2: 文字列基本操作 20点
Part 3: 文字列検索と置換 20点
Part 4: UTF-8処理 20点
**マンダトリー合計** **80点**
Bonus 1: 正規表現風パターンマッチング 10点
Bonus 2: 文字列圧縮と展開 10点
**ボーナス合計** **20点**

合格基準

  • マンダトリー: 64点以上で合格(80点満点の80%)
  • ボーナス: 追加評価(最終成績の加算)
  • 提出方法

    exercise08/
    ├── part1/
    │   └── array_ops.zig
    ├── part2/
    │   └── string_basic.zig
    ├── part3/
    │   └── string_search.zig
    ├── part4/
    │   └── utf8_ops.zig
    ├── bonus1/
    │   └── pattern_match.zig
    └── bonus2/
        └── compression.zig
    

    ヒント

  • 配列の反転:
var left: usize = 0;
var right = array.len - 1;
while (left < right) {
    // 要素を交換
    const temp = array[left];
    array[left] = array[right];
    array[right] = temp;
    left += 1;
    right -= 1;
}

  • 文字列の分割:
var result = std.ArrayList([]const u8).init(allocator);
var iter = std.mem.split(u8, str, delimiter);
while (iter.next()) |part| {
    try result.append(part);
}

  • UTF-8のイテレーション:
var view = try std.unicode.Utf8View.init(str);
var iter = view.iterator();
while (iter.nextCodepoint()) |codepoint| {
    // ...
}

  • Run-Length Encoding:
// 連続する文字をカウント
var count: usize = 1;
for (str[1..], 1..) |ch, i| {
    if (ch == str[i-1]) {
        count += 1;
    } else {
        // カウントと文字を出力
        count = 1;
    }
}

参考資料

  • 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