課題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