課題10: 構造体と列挙型の実践
マンダトリー要件 (80点)
Part 1: 幾何学ライブラリ (20点)
構造体を使った幾何学図形のライブラリを実装してください。
ファイル: part1/geometry.zig
const std = @import("std");
const Point = struct {
x: f64,
y: f64,
// TODO: 2点間の距離を計算
pub fn distance(self: Point, other: Point) f64 {
// 実装
}
// TODO: 原点からの距離
pub fn magnitude(self: Point) f64 {
// 実装
}
};
const Circle = struct {
center: Point,
radius: f64,
// TODO: 面積を計算
pub fn area(self: Circle) f64 {
// 実装
}
// TODO: 円周を計算
pub fn circumference(self: Circle) f64 {
// 実装
}
// TODO: 点が円の内部にあるか判定
pub fn contains(self: Circle, point: Point) bool {
// 実装
}
// TODO: 2つの円が重なっているか判定
pub fn overlaps(self: Circle, other: Circle) bool {
// 実装
}
};
const Rectangle = struct {
top_left: Point,
width: f64,
height: f64,
// TODO: 面積を計算
pub fn area(self: Rectangle) f64 {
// 実装
}
// TODO: 周囲の長さを計算
pub fn perimeter(self: Rectangle) f64 {
// 実装
}
// TODO: 点が矩形の内部にあるか判定
pub fn contains(self: Rectangle, point: Point) bool {
// 実装
}
// TODO: 中心点を取得
pub fn center(self: Rectangle) Point {
// 実装
}
};
const Triangle = struct {
p1: Point,
p2: Point,
p3: Point,
// TODO: 面積を計算(ヘロンの公式)
pub fn area(self: Triangle) f64 {
// 実装
}
// TODO: 周囲の長さを計算
pub fn perimeter(self: Triangle) f64 {
// 実装
}
};
pub fn main() void {
std.debug.print("=== Geometry Library ===\n\n", .{});
// Point
const p1 = Point{ .x = 0.0, .y = 0.0 };
const p2 = Point{ .x = 3.0, .y = 4.0 };
std.debug.print("Distance: {d:.2}\n", .{p1.distance(p2)});
// Circle
const circle1 = Circle{ .center = Point{ .x = 0.0, .y = 0.0 }, .radius = 5.0 };
const circle2 = Circle{ .center = Point{ .x = 8.0, .y = 0.0 }, .radius = 3.0 };
std.debug.print("\nCircle area: {d:.2}\n", .{circle1.area()});
std.debug.print("Circle circumference: {d:.2}\n", .{circle1.circumference()});
std.debug.print("Circles overlap: {}\n", .{circle1.overlaps(circle2)});
// Rectangle
const rect = Rectangle{ .top_left = Point{ .x = 0.0, .y = 0.0 }, .width = 10.0, .height = 5.0 };
std.debug.print("\nRectangle area: {d:.2}\n", .{rect.area()});
std.debug.print("Rectangle perimeter: {d:.2}\n", .{rect.perimeter()});
std.debug.print("Rectangle center: ({d:.2}, {d:.2})\n", .{rect.center().x, rect.center().y});
// Triangle
const tri = Triangle{
.p1 = Point{ .x = 0.0, .y = 0.0 },
.p2 = Point{ .x = 4.0, .y = 0.0 },
.p3 = Point{ .x = 2.0, .y = 3.0 },
};
std.debug.print("\nTriangle area: {d:.2}\n", .{tri.area()});
std.debug.print("Triangle perimeter: {d:.2}\n", .{tri.perimeter()});
}
Part 2: HTTPステータスモデル (20点)
列挙型とタグ付きユニオンを使ってHTTPレスポンスをモデリングしてください。
ファイル: part2/http.zig
const std = @import("std");
const HttpMethod = enum {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
// TODO: メソッドが安全か(副作用がないか)判定
pub fn isSafe(self: HttpMethod) bool {
// 実装
}
// TODO: メソッドがべき等か判定
pub fn isIdempotent(self: HttpMethod) bool {
// 実装
}
};
const HttpStatus = enum(u16) {
// 2xx Success
OK = 200,
Created = 201,
Accepted = 202,
NoContent = 204,
// 3xx Redirection
MovedPermanently = 301,
Found = 302,
NotModified = 304,
// 4xx Client Error
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
// 5xx Server Error
InternalServerError = 500,
NotImplemented = 501,
ServiceUnavailable = 503,
// TODO: ステータスが成功か判定
pub fn isSuccess(self: HttpStatus) bool {
// 実装
}
// TODO: ステータスがリダイレクトか判定
pub fn isRedirect(self: HttpStatus) bool {
// 実装
}
// TODO: ステータスがクライアントエラーか判定
pub fn isClientError(self: HttpStatus) bool {
// 実装
}
// TODO: ステータスがサーバーエラーか判定
pub fn isServerError(self: HttpStatus) bool {
// 実装
}
// TODO: ステータスメッセージを取得
pub fn message(self: HttpStatus) []const u8 {
return switch (self) {
.OK => "OK",
.Created => "Created",
.Accepted => "Accepted",
.NoContent => "No Content",
.MovedPermanently => "Moved Permanently",
.Found => "Found",
.NotModified => "Not Modified",
.BadRequest => "Bad Request",
.Unauthorized => "Unauthorized",
.Forbidden => "Forbidden",
.NotFound => "Not Found",
.MethodNotAllowed => "Method Not Allowed",
.InternalServerError => "Internal Server Error",
.NotImplemented => "Not Implemented",
.ServiceUnavailable => "Service Unavailable",
};
}
};
const HttpResponse = union(enum) {
Success: struct {
status: HttpStatus,
body: []const u8,
},
Redirect: struct {
status: HttpStatus,
location: []const u8,
},
Error: struct {
status: HttpStatus,
message: []const u8,
},
// TODO: レスポンスを表示
pub fn print(self: HttpResponse) void {
switch (self) {
.Success => |res| {
std.debug.print("Success: {} {s}\nBody: {s}\n",
.{@intFromEnum(res.status), res.status.message(), res.body});
},
.Redirect => |res| {
std.debug.print("Redirect: {} {s}\nLocation: {s}\n",
.{@intFromEnum(res.status), res.status.message(), res.location});
},
.Error => |res| {
std.debug.print("Error: {} {s}\nMessage: {s}\n",
.{@intFromEnum(res.status), res.status.message(), res.message});
},
}
}
};
pub fn main() void {
std.debug.print("=== HTTP Model ===\n\n", .{});
// HTTP Method
std.debug.print("GET is safe: {}\n", .{HttpMethod.GET.isSafe()});
std.debug.print("POST is idempotent: {}\n\n", .{HttpMethod.POST.isIdempotent()});
// HTTP Status
const status = HttpStatus.OK;
std.debug.print("Status: {} {s}\n", .{@intFromEnum(status), status.message()});
std.debug.print("Is success: {}\n\n", .{status.isSuccess()});
// HTTP Response
const responses = [_]HttpResponse{
HttpResponse{ .Success = .{ .status = .OK, .body = "Hello, World!" } },
HttpResponse{ .Redirect = .{ .status = .MovedPermanently, .location = "/new-url" } },
HttpResponse{ .Error = .{ .status = .NotFound, .message = "Page not found" } },
};
for (responses, 0..) |res, i| {
std.debug.print("Response #{}:\n", .{i + 1});
res.print();
std.debug.print("\n", .{});
}
}
Part 3: パック構造体でビットフィールド (20点)
パック構造体を使ってビットレベルのデータ構造を実装してください。
ファイル: part3/bitfields.zig
const std = @import("std");
// ファイルパーミッション(Unix形式)
const FilePermissions = packed struct {
// Others
others_execute: u1,
others_write: u1,
others_read: u1,
// Group
group_execute: u1,
group_write: u1,
group_read: u1,
// Owner
owner_execute: u1,
owner_write: u1,
owner_read: u1,
// TODO: 8進数表記に変換
pub fn toOctal(self: FilePermissions) u16 {
// 実装
}
// TODO: 8進数から作成
pub fn fromOctal(octal: u16) FilePermissions {
// 実装
}
// TODO: 文字列表記(例: "rwxr-xr--")
pub fn toString(self: FilePermissions, buf: []u8) []const u8 {
_ = self;
_ = buf;
// 実装
return "";
}
};
// IPv4アドレス
const IPv4Address = packed struct {
octet4: u8,
octet3: u8,
octet2: u8,
octet1: u8,
// TODO: u32に変換
pub fn toU32(self: IPv4Address) u32 {
return @bitCast(self);
}
// TODO: u32から作成
pub fn fromU32(value: u32) IPv4Address {
return @bitCast(value);
}
// TODO: 文字列表記(例: "192.168.1.1")
pub fn format(
self: IPv4Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
try writer.print("{}.{}.{}.{}", .{
self.octet1, self.octet2, self.octet3, self.octet4
});
}
};
// RGBカラー(24ビット)
const RGB = packed struct {
b: u8,
g: u8,
r: u8,
// TODO: u32に変換(0xRRGGBB形式)
pub fn toU32(self: RGB) u32 {
return @as(u32, self.r) << 16 | @as(u32, self.g) << 8 | @as(u32, self.b);
}
// TODO: u32から作成
pub fn fromU32(value: u32) RGB {
return RGB{
.r = @truncate((value >> 16) & 0xFF),
.g = @truncate((value >> 8) & 0xFF),
.b = @truncate(value & 0xFF),
};
}
// TODO: グレースケールに変換
pub fn toGrayscale(self: RGB) RGB {
// 実装: 0.299*R + 0.587*G + 0.114*B
}
};
pub fn main() void {
std.debug.print("=== Bit Fields ===\n\n", .{});
// File Permissions
const perms = FilePermissions.fromOctal(0o755);
std.debug.print("Permissions (octal): 0o{o}\n", .{perms.toOctal()});
var buf: [9]u8 = undefined;
std.debug.print("Permissions (string): {s}\n\n", .{perms.toString(&buf)});
// IPv4 Address
const ip = IPv4Address{ .octet1 = 192, .octet2 = 168, .octet3 = 1, .octet4 = 1 };
std.debug.print("IP Address: {}\n", .{ip});
std.debug.print("IP as u32: 0x{x}\n\n", .{ip.toU32()});
// RGB Color
const red = RGB{ .r = 255, .g = 0, .b = 0 };
std.debug.print("Red: 0x{x:0>6}\n", .{red.toU32()});
const gray = red.toGrayscale();
std.debug.print("Grayscale: R={}, G={}, B={}\n", .{gray.r, gray.g, gray.b});
}
Part 4: ジェネリックコレクション (20点)
ジェネリック構造体を使ってコレクションを実装してください。
ファイル: part4/collections.zig
const std = @import("std");
// ジェネリックなペア型
fn Pair(comptime T: type, comptime U: type) type {
return struct {
first: T,
second: U,
const Self = @This();
pub fn init(first: T, second: U) Self {
return Self{ .first = first, .second = second };
}
pub fn swap(self: Self) Pair(U, T) {
return Pair(U, T).init(self.second, self.first);
}
};
}
// ジェネリックなResult型
fn Result(comptime T: type, comptime E: type) type {
return union(enum) {
Ok: T,
Err: E,
const Self = @This();
// TODO: 成功か判定
pub fn isOk(self: Self) bool {
// 実装
}
// TODO: エラーか判定
pub fn isErr(self: Self) bool {
// 実装
}
// TODO: 値をアンラップ(エラーの場合はパニック)
pub fn unwrap(self: Self) T {
// 実装
}
// TODO: デフォルト値を返す
pub fn unwrapOr(self: Self, default: T) T {
// 実装
}
// TODO: map関数
pub fn map(self: Self, comptime R: type, func: fn(T) R) Result(R, E) {
// 実装
}
};
}
// ジェネリックなOption型
fn Option(comptime T: type) type {
return union(enum) {
Some: T,
None,
const Self = @This();
// TODO: 値があるか判定
pub fn isSome(self: Self) bool {
// 実装
}
// TODO: 値がないか判定
pub fn isNone(self: Self) bool {
// 実装
}
// TODO: 値をアンラップ
pub fn unwrap(self: Self) T {
// 実装
}
// TODO: デフォルト値を返す
pub fn unwrapOr(self: Self, default: T) T {
// 実装
}
// TODO: filter関数
pub fn filter(self: Self, predicate: fn(T) bool) Self {
// 実装
}
};
}
pub fn main() void {
std.debug.print("=== Generic Collections ===\n\n", .{});
// Pair
const pair1 = Pair(i32, []const u8).init(42, "answer");
std.debug.print("Pair: ({}, {s})\n", .{pair1.first, pair1.second});
const pair2 = pair1.swap();
std.debug.print("Swapped: ({s}, {})\n\n", .{pair2.first, pair2.second});
// Result
const ok = Result(i32, []const u8){ .Ok = 100 };
const err = Result(i32, []const u8){ .Err = "error" };
std.debug.print("Result is ok: {}\n", .{ok.isOk()});
std.debug.print("Result value: {}\n", .{ok.unwrapOr(0)});
std.debug.print("Error value: {}\n\n", .{err.unwrapOr(0)});
// Option
const some = Option(i32){ .Some = 42 };
const none = Option(i32).None;
std.debug.print("Option has value: {}\n", .{some.isSome()});
std.debug.print("Some value: {}\n", .{some.unwrapOr(0)});
std.debug.print("None value: {}\n", .{none.unwrapOr(0)});
}
ボーナス課題 (20点)
Bonus 1: Expression AST (10点)
タグ付きユニオンを使って数式の抽象構文木を実装してください。
ファイル: bonus1/ast.zig
const std = @import("std");
const Expr = union(enum) {
Number: i32,
Add: struct { left: *Expr, right: *Expr },
Sub: struct { left: *Expr, right: *Expr },
Mul: struct { left: *Expr, right: *Expr },
Div: struct { left: *Expr, right: *Expr },
// TODO: 式を評価
pub fn eval(self: *const Expr) i32 {
return switch (self.*) {
.Number => |n| n,
.Add => |bin| bin.left.eval() + bin.right.eval(),
.Sub => |bin| bin.left.eval() - bin.right.eval(),
.Mul => |bin| bin.left.eval() * bin.right.eval(),
.Div => |bin| @divTrunc(bin.left.eval(), bin.right.eval()),
};
}
// TODO: 式を文字列化
pub fn toString(self: *const Expr, allocator: std.mem.Allocator) ![]u8 {
_ = self;
_ = allocator;
// 実装
return "";
}
// TODO: 式の深さを取得
pub fn depth(self: *const Expr) usize {
// 実装
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
// (2 + 3) * 4
var num2 = Expr{ .Number = 2 };
var num3 = Expr{ .Number = 3 };
var add = Expr{ .Add = .{ .left = &num2, .right = &num3 } };
var num4 = Expr{ .Number = 4 };
var mul = Expr{ .Mul = .{ .left = &add, .right = &num4 } };
std.debug.print("Expression: {s}\n", .{try mul.toString(allocator)});
std.debug.print("Result: {}\n", .{mul.eval()});
std.debug.print("Depth: {}\n", .{mul.depth()});
}
Bonus 2: State Machine (10点)
列挙型とタグ付きユニオンを使って状態機械を実装してください。
ファイル: bonus2/state_machine.zig
const std = @import("std");
const State = enum {
Idle,
Running,
Paused,
Stopped,
Error,
};
const Event = union(enum) {
Start,
Pause,
Resume,
Stop,
Error: []const u8,
};
const StateMachine = struct {
state: State,
pub fn init() StateMachine {
return StateMachine{ .state = .Idle };
}
// TODO: イベントを処理して状態遷移
pub fn handleEvent(self: *StateMachine, event: Event) void {
std.debug.print("Current state: {}, Event: {}\n", .{self.state, event});
const new_state = switch (self.state) {
.Idle => switch (event) {
.Start => State.Running,
.Error => State.Error,
else => self.state,
},
.Running => switch (event) {
.Pause => State.Paused,
.Stop => State.Stopped,
.Error => State.Error,
else => self.state,
},
.Paused => switch (event) {
.Resume => State.Running,
.Stop => State.Stopped,
.Error => State.Error,
else => self.state,
},
.Stopped => switch (event) {
.Start => State.Running,
else => self.state,
},
.Error => switch (event) {
.Start => State.Idle,
else => self.state,
},
};
if (new_state != self.state) {
std.debug.print("State transition: {} -> {}\n", .{self.state, new_state});
self.state = new_state;
} else {
std.debug.print("Invalid transition, staying in {}\n", .{self.state});
}
std.debug.print("\n", .{});
}
// TODO: 有効な遷移か確認
pub fn canTransition(self: StateMachine, event: Event) bool {
// 実装
}
};
pub fn main() void {
std.debug.print("=== State Machine ===\n\n", .{});
var machine = StateMachine.init();
machine.handleEvent(Event.Start);
machine.handleEvent(Event.Pause);
machine.handleEvent(Event.Resume);
machine.handleEvent(Event.Stop);
machine.handleEvent(Event.Start);
machine.handleEvent(Event{ .Error = "Something went wrong" });
machine.handleEvent(Event.Start);
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: 幾何学ライブラリ | 20点 |
| Part 2: HTTPステータスモデル | 20点 |
| Part 3: パック構造体でビットフィールド | 20点 |
| Part 4: ジェネリックコレクション | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: Expression AST | 10点 |
| Bonus 2: State Machine | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise10/
├── part1/
│ └── geometry.zig
├── part2/
│ └── http.zig
├── part3/
│ └── bitfields.zig
├── part4/
│ └── collections.zig
├── bonus1/
│ └── ast.zig
└── bonus2/
└── state_machine.zig
テスト実行
# マンダトリー
zig run part1/geometry.zig
zig run part2/http.zig
zig run part3/bitfields.zig
zig run part4/collections.zig
# ボーナス
zig run bonus1/ast.zig
zig run bonus2/state_machine.zig