課題5: 制御構造の実践

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

Part 1: 条件分岐とループ (20点)

基本的な制御構造を使ったプログラムを実装してください。

ファイル: part1/control_basics.zig

const std = @import("std");

// TODO: 配列から最大値を見つける
fn findMax(numbers: []const i32) ?i32 {
    // 実装
}

// TODO: 配列から最小値を見つける
fn findMin(numbers: []const i32) ?i32 {
    // 実装
}

// TODO: 配列の合計を計算
fn sum(numbers: []const i32) i64 {
    // 実装
}

// TODO: 配列の平均を計算
fn average(numbers: []const i32) ?f64 {
    // 実装(空配列の場合はnull)
}

// TODO: 配列内の特定の値の出現回数を数える
fn count(numbers: []const i32, target: i32) usize {
    // 実装
}

pub fn main() void {
    const numbers = [_]i32{ 5, 2, 8, 1, 9, 3, 7 };

    if (findMax(&numbers)) |max| {
        std.debug.print("Max: {}\n", .{max});
    }

    if (findMin(&numbers)) |min| {
        std.debug.print("Min: {}\n", .{min});
    }

    std.debug.print("Sum: {}\n", .{sum(&numbers)});

    if (average(&numbers)) |avg| {
        std.debug.print("Average: {d:.2}\n", .{avg});
    }

    std.debug.print("Count of 5: {}\n", .{count(&numbers, 5)});
}

Part 2: switchによるパターンマッチング (20点)

switchを使った分類プログラムを実装してください。

ファイル: part2/pattern_matching.zig

const std = @import("std");

const Grade = enum {
    A,
    B,
    C,
    D,
    F,
};

// TODO: スコアから成績を判定
fn getGrade(score: u8) Grade {
    // 実装
    // 90-100: A
    // 80-89: B
    // 70-79: C
    // 60-69: D
    // 0-59: F
}

const DayOfWeek = enum {
    monday,
    tuesday,
    wednesday,
    thursday,
    friday,
    saturday,
    sunday,
};

// TODO: 曜日が平日か週末かを判定
fn isWeekend(day: DayOfWeek) bool {
    // 実装
}

const Season = enum {
    spring,
    summer,
    autumn,
    winter,
};

// TODO: 月から季節を判定(北半球)
fn getSeason(month: u8) ?Season {
    // 実装
    // 3-5: spring, 6-8: summer, 9-11: autumn, 12-2: winter
    // 範囲外はnull
}

pub fn main() void {
    std.debug.print("=== Grades ===\n", .{});
    const scores = [_]u8{ 95, 82, 75, 68, 55 };
    for (scores) |score| {
        const grade = getGrade(score);
        std.debug.print("Score {}: Grade {}\n", .{score, grade});
    }

    std.debug.print("\n=== Days ===\n", .{});
    const days = [_]DayOfWeek{
        .monday, .tuesday, .wednesday, .thursday,
        .friday, .saturday, .sunday
    };
    for (days) |day| {
        const weekend = isWeekend(day);
        std.debug.print("{}: {s}\n", .{day, if (weekend) "Weekend" else "Weekday"});
    }

    std.debug.print("\n=== Seasons ===\n", .{});
    for (1..13) |month| {
        if (getSeason(@intCast(month))) |season| {
            std.debug.print("Month {}: {}\n", .{month, season});
        }
    }
}

Part 3: ネストしたループ (20点)

ネストしたループを使ったプログラムを実装してください。

ファイル: part3/nested_loops.zig

const std = @import("std");

// TODO: 九九の表を出力
fn printMultiplicationTable() void {
    // 実装
    // 1x1=1  1x2=2  ... 1x9=9
    // 2x1=2  2x2=4  ... 2x9=18
    // ...
    // 9x1=9  9x2=18 ... 9x9=81
}

// TODO: 2次元配列の合計を計算
fn sumMatrix(matrix: []const []const i32) i64 {
    // 実装
}

// TODO: 2次元配列から最大値を見つける
fn findMaxInMatrix(matrix: []const []const i32) ?i32 {
    // 実装
}

// TODO: 素数を見つける(エラトステネスの篩)
fn findPrimes(allocator: std.mem.Allocator, max: usize) ![]bool {
    // 実装
    // 戻り値: bool配列(true=素数、false=合成数)
}

pub fn main() !void {
    std.debug.print("=== Multiplication Table ===\n", .{});
    printMultiplicationTable();

    std.debug.print("\n=== Matrix Operations ===\n", .{});
    const row1 = [_]i32{ 1, 2, 3 };
    const row2 = [_]i32{ 4, 5, 6 };
    const row3 = [_]i32{ 7, 8, 9 };
    const matrix = [_][]const i32{ &row1, &row2, &row3 };

    std.debug.print("Sum: {}\n", .{sumMatrix(&matrix)});
    if (findMaxInMatrix(&matrix)) |max| {
        std.debug.print("Max: {}\n", .{max});
    }

    std.debug.print("\n=== Primes up to 30 ===\n", .{});
    const allocator = std.heap.page_allocator;
    const primes = try findPrimes(allocator, 30);
    defer allocator.free(primes);

    for (primes, 0..) |is_prime, i| {
        if (is_prime) {
            std.debug.print("{} ", .{i});
        }
    }
    std.debug.print("\n", .{});
}

Part 4: エラーハンドリングと制御フロー (20点)

エラーハンドリングを組み合わせた制御フローを実装してください。

ファイル: part4/error_control.zig

const std = @import("std");

const ParseError = error{
    InvalidCharacter,
    Overflow,
    EmptyString,
};

// TODO: 文字列を整数にパース(エラーハンドリング付き)
fn parseInt(str: []const u8) ParseError!i32 {
    // 実装
    // 空文字列: EmptyString
    // 無効な文字: InvalidCharacter
    // オーバーフロー: Overflow
}

const MathError = error{
    DivisionByZero,
    NegativeSquareRoot,
};

// TODO: 安全な除算
fn safeDivide(a: f64, b: f64) MathError!f64 {
    // 実装
}

// TODO: 平方根(負の数はエラー)
fn safeSqrt(x: f64) MathError!f64 {
    // 実装
}

// TODO: 配列から値を検索(見つからない場合はエラー)
fn findValue(numbers: []const i32, target: i32) !usize {
    // 実装
}

pub fn main() !void {
    std.debug.print("=== Parse Int ===\n", .{});
    const test_strings = [_][]const u8{ "123", "-456", "abc", "999999999999" };
    for (test_strings) |str| {
        if (parseInt(str)) |value| {
            std.debug.print("'{s}' -> {}\n", .{str, value});
        } else |err| {
            std.debug.print("'{s}' -> Error: {}\n", .{str, err});
        }
    }

    std.debug.print("\n=== Safe Math ===\n", .{});
    if (safeDivide(10.0, 2.0)) |result| {
        std.debug.print("10 / 2 = {d:.2}\n", .{result});
    } else |err| {
        std.debug.print("Error: {}\n", .{err});
    }

    if (safeDivide(10.0, 0.0)) |result| {
        std.debug.print("10 / 0 = {d:.2}\n", .{result});
    } else |err| {
        std.debug.print("10 / 0 -> Error: {}\n", .{err});
    }

    if (safeSqrt(-4.0)) |result| {
        std.debug.print("sqrt(-4) = {d:.2}\n", .{result});
    } else |err| {
        std.debug.print("sqrt(-4) -> Error: {}\n", .{err});
    }

    std.debug.print("\n=== Find Value ===\n", .{});
    const numbers = [_]i32{ 1, 2, 3, 4, 5 };
    if (findValue(&numbers, 3)) |index| {
        std.debug.print("Found 3 at index {}\n", .{index});
    } else |err| {
        std.debug.print("Error: {}\n", .{err});
    }
}

ボーナス課題 (20点)

Bonus 1: 状態機械の実装 (10点)

状態機械を使った信号機シミュレーションを実装してください。

ファイル: bonus1/state_machine.zig

const std = @import("std");

const TrafficLight = enum {
    red,
    yellow,
    green,
};

const LightState = struct {
    current: TrafficLight,
    duration: u32,  // 秒数

    // TODO: 次の状態に遷移
    pub fn next(self: *LightState) void {
        // 実装
        // red (30秒) -> green (45秒) -> yellow (5秒) -> red
    }

    // TODO: 現在の状態を表示
    pub fn display(self: LightState) void {
        // 実装
    }

    // TODO: 時間を進める(1秒)
    pub fn tick(self: *LightState) void {
        // 実装
    }
};

pub fn main() void {
    var light = LightState{
        .current = .red,
        .duration = 30,
    };

    // 100秒間シミュレート
    for (0..100) |second| {
        std.debug.print("Time {}: ", .{second});
        light.display();
        light.tick();
    }
}

Bonus 2: 簡易電卓 (10点)

再帰下降パーサーを使った簡易電卓を実装してください。

ファイル: bonus2/calculator.zig

const std = @import("std");

const CalcError = error{
    InvalidExpression,
    DivisionByZero,
    UnknownOperator,
};

const Token = union(enum) {
    number: i32,
    operator: u8,
    end,
};

const Parser = struct {
    input: []const u8,
    pos: usize,

    // TODO: 次のトークンを取得
    fn nextToken(self: *Parser) Token {
        // 実装
    }

    // TODO: 式をパース(加算・減算)
    fn parseExpression(self: *Parser) CalcError!i32 {
        // 実装
    }

    // TODO: 項をパース(乗算・除算)
    fn parseTerm(self: *Parser) CalcError!i32 {
        // 実装
    }

    // TODO: 因子をパース(数値)
    fn parseFactor(self: *Parser) CalcError!i32 {
        // 実装
    }
};

// TODO: 式を評価
fn evaluate(expression: []const u8) CalcError!i32 {
    var parser = Parser{
        .input = expression,
        .pos = 0,
    };
    return parser.parseExpression();
}

pub fn main() !void {
    const expressions = [_][]const u8{
        "2+3",
        "10-5",
        "4*5",
        "20/4",
        "2+3*4",
        "10-2*3",
    };

    for (expressions) |expr| {
        if (evaluate(expr)) |result| {
            std.debug.print("{s} = {}\n", .{expr, result});
        } else |err| {
            std.debug.print("{s} -> Error: {}\n", .{expr, err});
        }
    }
}

評価基準

項目 配点
Part 1: 条件分岐とループ 20点
Part 2: switchによるパターンマッチング 20点
Part 3: ネストしたループ 20点
Part 4: エラーハンドリングと制御フロー 20点
**マンダトリー合計** **80点**
Bonus 1: 状態機械の実装 10点
Bonus 2: 簡易電卓 10点
**ボーナス合計** **20点**

合格基準

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

    exercise05/
    ├── part1/
    │   └── control_basics.zig
    ├── part2/
    │   └── pattern_matching.zig
    ├── part3/
    │   └── nested_loops.zig
    ├── part4/
    │   └── error_control.zig
    ├── bonus1/
    │   └── state_machine.zig
    └── bonus2/
        └── calculator.zig
    

    テスト実行

    # マンダトリー
    zig run part1/control_basics.zig
    zig run part2/pattern_matching.zig
    zig run part3/nested_loops.zig
    zig run part4/error_control.zig
    
    # ボーナス
    zig run bonus1/state_machine.zig
    zig run bonus2/calculator.zig
    

    ヒント

  • 配列のイテレーション:
for (array) |item| {
    // itemを使用
}

for (array, 0..) |item, index| {
    // itemとindexを使用
}

  • switchでの範囲マッチング:
switch (value) {
    0...10 => {},
    11...20 => {},
    else => {},
}

  • エラーハンドリング:
if (riskyFunc()) |value| {
    // 成功時の処理
} else |err| {
    // エラー時の処理
}

  • オプショナルのアンラップ:
if (optional_value) |value| {
    // 値が存在する場合
} else {
    // nullの場合
}

  • エラトステネスの篩:
// すべてをtrueで初期化
// 2から始めて、その倍数をfalseにマーク
// 次の素数に移動して繰り返し

参考資料

  • Zig Language Reference - Control Flow: https://ziglang.org/documentation/master/#Control-Flow
  • Ziglearn - Flow Control: https://ziglearn.org/chapter-1/#flow-control
  • Ziglearn - Switch: https://ziglearn.org/chapter-1/#switch