課題6: 関数の実践
マンダトリー要件 (80点)
Part 1: 基本的な数学関数 (20点)
基本的な数学関数を実装してください。
ファイル: part1/math_basic.zig
const std = @import("std");
// TODO: 最大公約数(ユークリッドの互除法)
fn gcd(a: u64, b: u64) u64 {
// 実装
}
// TODO: 最小公倍数
fn lcm(a: u64, b: u64) u64 {
// 実装
// ヒント: lcm(a, b) = (a * b) / gcd(a, b)
}
// TODO: べき乗計算
fn power(base: i64, exp: u32) i64 {
// 実装(繰り返し二乗法を推奨)
}
// TODO: 階乗
fn factorial(n: u64) u64 {
// 実装
}
// TODO: 組み合わせ C(n, k) = n! / (k! * (n-k)!)
fn combination(n: u64, k: u64) u64 {
// 実装
}
pub fn main() void {
std.debug.print("=== GCD/LCM ===\n", .{});
std.debug.print("gcd(48, 18) = {}\n", .{gcd(48, 18)});
std.debug.print("lcm(12, 18) = {}\n", .{lcm(12, 18)});
std.debug.print("\n=== Power ===\n", .{});
std.debug.print("2^10 = {}\n", .{power(2, 10)});
std.debug.print("3^5 = {}\n", .{power(3, 5)});
std.debug.print("\n=== Factorial ===\n", .{});
std.debug.print("5! = {}\n", .{factorial(5)});
std.debug.print("10! = {}\n", .{factorial(10)});
std.debug.print("\n=== Combination ===\n", .{});
std.debug.print("C(5, 2) = {}\n", .{combination(5, 2)});
std.debug.print("C(10, 3) = {}\n", .{combination(10, 3)});
}
Part 2: comptime関数 (20点)
コンパイル時計算を活用した関数を実装してください。
ファイル: part2/comptime_funcs.zig
const std = @import("std");
// TODO: コンパイル時にフィボナッチ数を計算
fn fibonacci(comptime n: u32) u64 {
// 実装
}
// TODO: コンパイル時に素数判定
fn isPrime(comptime n: u64) bool {
// 実装
}
// TODO: ジェネリック関数: 配列の合計
fn sum(comptime T: type, array: []const T) T {
// 実装
}
// TODO: ジェネリック関数: 配列の平均
fn average(comptime T: type, array: []const T) ?T {
// 実装(空配列の場合はnull)
}
// TODO: ジェネリック関数: 配列の最大値と最小値を返す
fn minMax(comptime T: type, array: []const T) ?struct { min: T, max: T } {
// 実装
}
pub fn main() void {
std.debug.print("=== Compile-time Fibonacci ===\n", .{});
const fib_10 = fibonacci(10);
const fib_20 = fibonacci(20);
std.debug.print("fib(10) = {}\n", .{fib_10});
std.debug.print("fib(20) = {}\n", .{fib_20});
std.debug.print("\n=== Compile-time Prime Check ===\n", .{});
std.debug.print("17 is prime: {}\n", .{isPrime(17)});
std.debug.print("18 is prime: {}\n", .{isPrime(18)});
std.debug.print("\n=== Generic Sum ===\n", .{});
const int_arr = [_]i32{ 1, 2, 3, 4, 5 };
const float_arr = [_]f64{ 1.5, 2.5, 3.5 };
std.debug.print("Sum of integers: {}\n", .{sum(i32, &int_arr)});
std.debug.print("Sum of floats: {d:.1}\n", .{sum(f64, &float_arr)});
std.debug.print("\n=== Generic Average ===\n", .{});
if (average(i32, &int_arr)) |avg| {
std.debug.print("Average: {}\n", .{avg});
}
std.debug.print("\n=== Generic Min/Max ===\n", .{});
if (minMax(i32, &int_arr)) |result| {
std.debug.print("Min: {}, Max: {}\n", .{result.min, result.max});
}
}
Part 3: 高階関数 (20点)
関数を引数に取る高階関数を実装してください。
ファイル: part3/higher_order.zig
const std = @import("std");
// TODO: map関数(配列の各要素に関数を適用)
fn map(
comptime T: type,
comptime R: type,
allocator: std.mem.Allocator,
array: []const T,
func: *const fn(T) R,
) ![]R {
// 実装
}
// TODO: filter関数(条件を満たす要素を抽出)
fn filter(
comptime T: type,
allocator: std.mem.Allocator,
array: []const T,
predicate: *const fn(T) bool,
) ![]T {
// 実装
}
// TODO: reduce関数(配列を単一の値に集約)
fn reduce(
comptime T: type,
comptime R: type,
array: []const T,
initial: R,
func: *const fn(R, T) R,
) R {
// 実装
}
// TODO: forEach関数(各要素に対して処理を実行)
fn forEach(
comptime T: type,
array: []const T,
callback: *const fn(T) void,
) void {
// 実装
}
// テスト用の関数
fn double(x: i32) i32 {
return x * 2;
}
fn isEven(x: i32) bool {
return @mod(x, 2) == 0;
}
fn add(acc: i32, x: i32) i32 {
return acc + x;
}
fn printValue(x: i32) void {
std.debug.print("{} ", .{x});
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
std.debug.print("=== Map ===\n", .{});
const doubled = try map(i32, i32, allocator, &numbers, &double);
defer allocator.free(doubled);
std.debug.print("Doubled: ", .{});
for (doubled) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("\n=== Filter ===\n", .{});
const evens = try filter(i32, allocator, &numbers, &isEven);
defer allocator.free(evens);
std.debug.print("Evens: ", .{});
for (evens) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("\n=== Reduce ===\n", .{});
const sum_result = reduce(i32, i32, &numbers, 0, &add);
std.debug.print("Sum: {}\n", .{sum_result});
std.debug.print("\n=== ForEach ===\n", .{});
std.debug.print("Numbers: ", .{});
forEach(i32, &numbers, &printValue);
std.debug.print("\n", .{});
}
Part 4: 再帰関数 (20点)
再帰を使った関数を実装してください。
ファイル: part4/recursion.zig
const std = @import("std");
// TODO: ハノイの塔
fn hanoi(n: u32, from: []const u8, to: []const u8, aux: []const u8) void {
// 実装
// n枚のディスクを from から to へ移動(aux を補助として使用)
}
// TODO: 二分探索(再帰版)
fn binarySearch(comptime T: type, array: []const T, target: T, left: usize, right: usize) ?usize {
// 実装
}
// TODO: クイックソート
fn quickSort(comptime T: type, array: []T) void {
if (array.len <= 1) return;
// 実装
}
// TODO: マージソート
fn mergeSort(comptime T: type, allocator: std.mem.Allocator, array: []T) !void {
if (array.len <= 1) return;
// 実装
}
// TODO: パスカルの三角形のn行目を生成
fn pascalRow(allocator: std.mem.Allocator, n: usize) ![]u64 {
// 実装
}
pub fn main() !void {
std.debug.print("=== Hanoi Tower ===\n", .{});
hanoi(3, "A", "C", "B");
std.debug.print("\n=== Binary Search ===\n", .{});
const sorted = [_]i32{ 1, 3, 5, 7, 9, 11, 13, 15 };
if (binarySearch(i32, &sorted, 7, 0, sorted.len)) |index| {
std.debug.print("Found 7 at index {}\n", .{index});
}
std.debug.print("\n=== Quick Sort ===\n", .{});
var arr1 = [_]i32{ 5, 2, 8, 1, 9, 3 };
quickSort(i32, &arr1);
std.debug.print("Sorted: ", .{});
for (arr1) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("\n=== Merge Sort ===\n", .{});
const allocator = std.heap.page_allocator;
var arr2 = [_]i32{ 5, 2, 8, 1, 9, 3 };
try mergeSort(i32, allocator, &arr2);
std.debug.print("Sorted: ", .{});
for (arr2) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("\n=== Pascal's Triangle ===\n", .{});
for (0..6) |i| {
const row = try pascalRow(allocator, i);
defer allocator.free(row);
for (row) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
}
}
ボーナス課題 (20点)
Bonus 1: メモ化フィボナッチ (10点)
メモ化を使った効率的なフィボナッチ関数を実装してください。
ファイル: bonus1/memoization.zig
const std = @import("std");
const Memoizer = struct {
cache: std.AutoHashMap(u64, u64),
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Memoizer {
return Memoizer{
.cache = std.AutoHashMap(u64, u64).init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *Memoizer) void {
self.cache.deinit();
}
// TODO: メモ化付きフィボナッチ
pub fn fibonacci(self: *Memoizer, n: u64) !u64 {
// 実装
// 1. キャッシュをチェック
// 2. キャッシュにあれば返す
// 3. なければ計算してキャッシュに保存
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var memo = Memoizer.init(allocator);
defer memo.deinit();
std.debug.print("Memoized Fibonacci:\n", .{});
for (0..45) |i| {
const result = try memo.fibonacci(@intCast(i));
std.debug.print("fib({}) = {}\n", .{i, result});
}
}
Bonus 2: 関数合成 (10点)
関数合成を実装してください。
ファイル: bonus2/composition.zig
const std = @import("std");
// TODO: 2つの関数を合成
fn compose(
comptime A: type,
comptime B: type,
comptime C: type,
f: *const fn(B) C,
g: *const fn(A) B,
) fn(A) C {
// 実装
// (f ∘ g)(x) = f(g(x))
}
// TODO: カリー化(部分適用)
fn curry(
comptime A: type,
comptime B: type,
comptime R: type,
comptime a_val: A,
) fn(B) R {
// 実装
}
// テスト用関数
fn addOne(x: i32) i32 {
return x + 1;
}
fn double(x: i32) i32 {
return x * 2;
}
fn square(x: i32) i32 {
return x * x;
}
pub fn main() void {
// 関数合成のテスト
const addOneThenDouble = compose(i32, i32, i32, &double, &addOne);
const doubleThenSquare = compose(i32, i32, i32, &square, &double);
std.debug.print("(addOne then double)(5) = {}\n", .{addOneThenDouble(5)});
std.debug.print("(double then square)(5) = {}\n", .{doubleThenSquare(5)});
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: 基本的な数学関数 | 20点 |
| Part 2: comptime関数 | 20点 |
| Part 3: 高階関数 | 20点 |
| Part 4: 再帰関数 | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: メモ化フィボナッチ | 10点 |
| Bonus 2: 関数合成 | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise06/
├── part1/
│ └── math_basic.zig
├── part2/
│ └── comptime_funcs.zig
├── part3/
│ └── higher_order.zig
├── part4/
│ └── recursion.zig
├── bonus1/
│ └── memoization.zig
└── bonus2/
└── composition.zig
テスト実行
# マンダトリー
zig run part1/math_basic.zig
zig run part2/comptime_funcs.zig
zig run part3/higher_order.zig
zig run part4/recursion.zig
# ボーナス
zig run bonus1/memoization.zig
zig run bonus2/composition.zig
ヒント
fn gcd(a: u64, b: u64) u64 {
if (b == 0) return a;
return gcd(b, a % b);
}
- 繰り返し二乗法:
// power(2, 10)の場合
// 2^10 = (2^5)^2 = ((2^2)^2 * 2)^2
- 高階関数のアロケーション:
var result = std.ArrayList(T).init(allocator);
defer result.deinit();
try result.append(item);
return result.toOwnedSlice();
- ハノイの塔:
// n=1: from -> to
// n>1:
// 1. n-1枚を from -> aux
// 2. 1枚を from -> to
// 3. n-1枚を aux -> to
参考資料
- Zig Language Reference - Functions: https://ziglang.org/documentation/master/#Functions
- Ziglearn - Generic Types: https://ziglearn.org/chapter-2/#generic-types
- Ziglearn - Comptime: https://ziglearn.org/chapter-2/#comptime