Skip to content

第2章 練習問題 - 呪力操作の試練

所有権・借用・ライフタイムの総合演習

第2章で学んだ呪力操作編の内容を実践で確認しよう。所有権、借用、ライフタイム - これらは俺の無下限術式と同じく、一度マスターすれば絶対的な力になる。

五条先生からのアドバイス

この章の問題は少し手強いぞ。でも心配するな。基本を理解していれば必ず解ける。 コンパイラのエラーメッセージをよく読んで、何が問題なのかを理解することも大切だ。

初級編 - 所有権の基本操作

問題1: 呪力の移譲システム

2人の呪術師間で呪力を移譲するシステムを作成せよ。以下の仕様を満たすこと:

  • 呪術師は名前と呪力値を持つ
  • 呪力を他の呪術師に移譲する関数
  • 移譲後は元の呪術師の呪力は0になる
struct Sorcerer {
    name: String,
    power: i32,
}

// ここに実装
fn main() {
    let mut gojo = Sorcerer {
        name: String::from("五条悟"),
        power: 2000,
    };

    let mut yuji = Sorcerer {
        name: String::from("虎杖悠仁"),
        power: 800,
    };

    // 呪力移譲の実装
}
解答を見る
struct Sorcerer {
    name: String,
    power: i32,
}

impl Sorcerer {
    fn transfer_power(&mut self, amount: i32, recipient: &mut Sorcerer) -> bool {
        if self.power >= amount {
            self.power -= amount;
            recipient.power += amount;
            println!("{} が {} に {}の呪力を移譲",
                     self.name, recipient.name, amount);
            true
        } else {
            println!("{} の呪力が不足しています", self.name);
            false
        }
    }

    fn display_status(&self) {
        println!("{}: 呪力 {}", self.name, self.power);
    }
}

fn main() {
    let mut gojo = Sorcerer {
        name: String::from("五条悟"),
        power: 2000,
    };

    let mut yuji = Sorcerer {
        name: String::from("虎杖悠仁"),
        power: 800,
    };

    println!("=== 移譲前 ===");
    gojo.display_status();
    yuji.display_status();

    gojo.transfer_power(500, &mut yuji);

    println!("\\n=== 移譲後 ===");
    gojo.display_status();
    yuji.display_status();
}

問題2: 術式コレクションの管理

術式名のベクターを管理するシステムで、所有権の移動を正しく処理せよ。

fn process_techniques(techniques: Vec<String>) -> Vec<String> {
    // 各術式に "習得済み:" を前置
    // 処理後のベクターを返す
}

fn count_powerful_techniques(techniques: &Vec<String>) -> usize {
    // "術式"を含む技の数を数える
}

fn main() {
    let techniques = vec![
        String::from("術式順転『蒼』"),
        String::from("基本攻撃"),
        String::from("術式反転『赫』"),
        String::from("防御技"),
    ];

    // ここに実装
}
解答を見る
fn process_techniques(techniques: Vec<String>) -> Vec<String> {
    techniques.into_iter()
        .map(|tech| format!("習得済み: {}", tech))
        .collect()
}

fn count_powerful_techniques(techniques: &Vec<String>) -> usize {
    techniques.iter()
        .filter(|tech| tech.contains("術式"))
        .count()
}

fn main() {
    let techniques = vec![
        String::from("術式順転『蒼』"),
        String::from("基本攻撃"),
        String::from("術式反転『赫』"),
        String::from("防御技"),
    ];

    println!("元の技: {:?}", techniques);

    // 借用で技の数を数える
    let powerful_count = count_powerful_techniques(&techniques);
    println!("術式の数: {}", powerful_count);

    // 所有権を移動して処理
    let processed = process_techniques(techniques);
    println!("処理後: {:?}", processed);

    // techniques はもう使えない(所有権が移動した)
    // println!("{:?}", techniques); // エラー!
}

中級編 - 借用と参照の活用

問題3: 戦闘ログ分析システム

戦闘ログを分析して統計情報を提供するシステムを作成せよ。借用を適切に使用すること。

struct BattleLog {
    entries: Vec<String>,
}

impl BattleLog {
    fn new() -> Self {
        BattleLog {
            entries: Vec::new(),
        }
    }

    fn add_entry(&mut self, entry: String) {
        self.entries.push(entry);
    }
}

// 以下の関数を実装せよ:
// 1. ログの総数を返す関数(不変借用)
// 2. 特定のキーワードを含むエントリ数を返す関数(不変借用)
// 3. 最後のN件のエントリを返す関数(スライス)
// 4. すべてのエントリに接頭辞を追加する関数(可変借用)

fn main() {
    let mut log = BattleLog::new();
    log.add_entry(String::from("五条悟が術式順転『蒼』を使用"));
    log.add_entry(String::from("宿儺が解を発動"));
    log.add_entry(String::from("五条悟が術式反転『赫』を使用"));

    // テスト用コード
}
解答を見る
struct BattleLog {
    entries: Vec<String>,
}

impl BattleLog {
    fn new() -> Self {
        BattleLog {
            entries: Vec::new(),
        }
    }

    fn add_entry(&mut self, entry: String) {
        self.entries.push(entry);
    }
}

// 1. ログの総数を返す(不変借用)
fn count_entries(log: &BattleLog) -> usize {
    log.entries.len()
}

// 2. キーワードを含むエントリ数(不変借用)
fn count_entries_with_keyword(log: &BattleLog, keyword: &str) -> usize {
    log.entries.iter()
        .filter(|entry| entry.contains(keyword))
        .count()
}

// 3. 最後のN件のエントリ(スライス)
fn get_recent_entries(log: &BattleLog, count: usize) -> &[String] {
    let start = log.entries.len().saturating_sub(count);
    &log.entries[start..]
}

// 4. すべてのエントリに接頭辞を追加(可変借用)
fn add_prefix_to_all(log: &mut BattleLog, prefix: &str) {
    for entry in &mut log.entries {
        *entry = format!("{}: {}", prefix, entry);
    }
}

// 5. 特定の条件のエントリを取得(不変借用)
fn find_entries_by_user<'a>(log: &'a BattleLog, user: &str) -> Vec<&'a String> {
    log.entries.iter()
        .filter(|entry| entry.contains(user))
        .collect()
}

fn main() {
    let mut log = BattleLog::new();
    log.add_entry(String::from("五条悟が術式順転『蒼』を使用"));
    log.add_entry(String::from("宿儺が解を発動"));
    log.add_entry(String::from("五条悟が術式反転『赫』を使用"));
    log.add_entry(String::from("虎杖悠仁が黒閃を発動"));

    println!("=== 戦闘ログ分析 ===");

    // 統計情報
    println!("総エントリ数: {}", count_entries(&log));
    println!("五条悟の行動: {}回", count_entries_with_keyword(&log, "五条悟"));
    println!("術式使用: {}回", count_entries_with_keyword(&log, "術式"));

    // 最近のエントリ
    let recent = get_recent_entries(&log, 2);
    println!("\\n最近の2件:");
    for (i, entry) in recent.iter().enumerate() {
        println!("  {}. {}", i + 1, entry);
    }

    // 五条悟の行動のみ
    let gojo_entries = find_entries_by_user(&log, "五条悟");
    println!("\\n五条悟の行動:");
    for entry in gojo_entries {
        println!("  - {}", entry);
    }

    // 接頭辞追加
    add_prefix_to_all(&mut log, "[戦闘記録]");
    println!("\\n接頭辞追加後の最初のエントリ:");
    if let Some(first) = log.entries.first() {
        println!("  {}", first);
    }
}

問題4: 文字列スライス操作

文字列スライスを使って術式名を解析する関数群を作成せよ。

// 以下の関数を実装せよ:
// 1. 術式名から種類を抽出("術式順転『蒼』" → "順転")
// 2. 術式名から技名を抽出("術式順転『蒼』" → "蒼")
// 3. 複数の術式名の中から最も長いものを返す
// 4. 術式名が有効かチェック(「術式」と「『』」を含む)

fn main() {
    let techniques = [
        "術式順転『蒼』",
        "術式反転『赫』",
        "虚式『茈』",
        "無効な技名",
        "基本攻撃",
    ];

    // テスト用コード
}
解答を見る
// 1. 術式の種類を抽出
fn extract_technique_type(technique: &str) -> Option<&str> {
    if let Some(start) = technique.find("術式") {
        let after_jutsu = &technique[start + 6..]; // "術式"の後
        if let Some(end) = after_jutsu.find("『") {
            return Some(&after_jutsu[..end]);
        }
    }
    None
}

// 2. 技名を抽出
fn extract_technique_name(technique: &str) -> Option<&str> {
    if let Some(start) = technique.find("『") {
        if let Some(end) = technique.find("』") {
            if start < end {
                return Some(&technique[start + 3..end]); // 『の後から』の前まで
            }
        }
    }
    None
}

// 3. 最も長い術式名を返す
fn find_longest_technique<'a>(techniques: &[&'a str]) -> Option<&'a str> {
    techniques.iter()
        .max_by_key(|tech| tech.len())
        .copied()
}

// 4. 術式名の有効性チェック
fn is_valid_technique(technique: &str) -> bool {
    technique.contains("術式") && technique.contains("『") && technique.contains("』")
}

// 5. 術式を詳細分析
fn analyze_technique(technique: &str) -> String {
    let mut analysis = String::new();

    analysis.push_str(&format!("技名: {}\\n", technique));
    analysis.push_str(&format!("文字数: {}\\n", technique.chars().count()));
    analysis.push_str(&format!("有効: {}\\n", is_valid_technique(technique)));

    if let Some(tech_type) = extract_technique_type(technique) {
        analysis.push_str(&format!("種類: {}\\n", tech_type));
    }

    if let Some(name) = extract_technique_name(technique) {
        analysis.push_str(&format!("技名: {}\\n", name));
    }

    analysis
}

// 6. 複数技の統計
fn generate_technique_stats(techniques: &[&str]) -> String {
    let valid_count = techniques.iter()
        .filter(|&&tech| is_valid_technique(tech))
        .count();

    let total_chars: usize = techniques.iter()
        .map(|tech| tech.chars().count())
        .sum();

    let avg_length = if techniques.is_empty() {
        0.0
    } else {
        total_chars as f64 / techniques.len() as f64
    };

    format!(
        "技数: {}\\n有効技数: {}\\n平均文字数: {:.1}\\n最長技: {}",
        techniques.len(),
        valid_count,
        avg_length,
        find_longest_technique(techniques).unwrap_or("なし")
    )
}

fn main() {
    let techniques = [
        "術式順転『蒼』",
        "術式反転『赫』",
        "虚式『茈』",
        "無効な技名",
        "基本攻撃",
        "無下限呪術『紫』",
    ];

    println!("=== 術式分析システム ===");

    // 各技の詳細分析
    for technique in &techniques {
        println!("\\n--- {} ---", technique);

        if let Some(tech_type) = extract_technique_type(technique) {
            println!("種類: {}", tech_type);
        }

        if let Some(name) = extract_technique_name(technique) {
            println!("技名: {}", name);
        }

        println!("有効: {}", is_valid_technique(technique));
    }

    // 統計情報
    println!("\\n=== 統計情報 ===");
    println!("{}", generate_technique_stats(&techniques));

    // 有効な技のみフィルタ
    let valid_techniques: Vec<&str> = techniques.iter()
        .filter(|&&tech| is_valid_technique(tech))
        .copied()
        .collect();

    println!("\\n=== 有効な術式一覧 ===");
    for (i, technique) in valid_techniques.iter().enumerate() {
        println!("{}. {}", i + 1, technique);
    }
}

上級編 - ライフタイムの実践

問題5: 呪術師データベース

ライフタイム注釈を使って、呪術師の情報を管理するデータベースシステムを作成せよ。

// 呪術師の参照を保持する構造体
struct SorcererRef<'a> {
    name: &'a str,
    grade: &'a str,
    techniques: Vec<&'a str>,
}

// データベース構造体
struct SorcererDatabase<'a> {
    sorcerers: Vec<SorcererRef<'a>>,
}

// 以下のメソッドを実装せよ:
// 1. 新しい呪術師を追加
// 2. 名前で検索
// 3. 等級で検索
// 4. 特定の技を使える呪術師を検索
// 5. 最も技数の多い呪術師を返す

fn main() {
    // テスト用データ
    let names = ["五条悟", "両面宿儺", "虎杖悠仁"];
    let grades = ["特級", "特級", "1級"];
    let techniques = [
        vec!["無下限呪術", "術式順転『蒼』", "術式反転『赫』"],
        vec!["解", "捌", "伏魔御廚子"],
        vec!["黒閃", "発散"],
    ];

    // データベース操作のテスト
}
解答を見る
#[derive(Debug)]
struct SorcererRef<'a> {
    name: &'a str,
    grade: &'a str,
    techniques: Vec<&'a str>,
}

impl<'a> SorcererRef<'a> {
    fn new(name: &'a str, grade: &'a str) -> Self {
        SorcererRef {
            name,
            grade,
            techniques: Vec::new(),
        }
    }

    fn add_technique(&mut self, technique: &'a str) {
        self.techniques.push(technique);
    }

    fn has_technique(&self, technique: &str) -> bool {
        self.techniques.iter().any(|&t| t == technique)
    }

    fn technique_count(&self) -> usize {
        self.techniques.len()
    }
}

struct SorcererDatabase<'a> {
    sorcerers: Vec<SorcererRef<'a>>,
}

impl<'a> SorcererDatabase<'a> {
    fn new() -> Self {
        SorcererDatabase {
            sorcerers: Vec::new(),
        }
    }

    // 1. 新しい呪術師を追加
    fn add_sorcerer(&mut self, sorcerer: SorcererRef<'a>) {
        self.sorcerers.push(sorcerer);
    }

    // 2. 名前で検索
    fn find_by_name(&self, name: &str) -> Option<&SorcererRef<'a>> {
        self.sorcerers.iter()
            .find(|sorcerer| sorcerer.name == name)
    }

    // 3. 等級で検索
    fn find_by_grade(&self, grade: &str) -> Vec<&SorcererRef<'a>> {
        self.sorcerers.iter()
            .filter(|sorcerer| sorcerer.grade == grade)
            .collect()
    }

    // 4. 特定の技を使える呪術師を検索
    fn find_by_technique(&self, technique: &str) -> Vec<&SorcererRef<'a>> {
        self.sorcerers.iter()
            .filter(|sorcerer| sorcerer.has_technique(technique))
            .collect()
    }

    // 5. 最も技数の多い呪術師を返す
    fn find_most_skilled(&self) -> Option<&SorcererRef<'a>> {
        self.sorcerers.iter()
            .max_by_key(|sorcerer| sorcerer.technique_count())
    }

    // 6. 統計情報を生成
    fn generate_statistics(&self) -> DatabaseStats<'a> {
        DatabaseStats {
            database: self,
        }
    }
}

struct DatabaseStats<'a> {
    database: &'a SorcererDatabase<'a>,
}

impl<'a> DatabaseStats<'a> {
    fn total_sorcerers(&self) -> usize {
        self.database.sorcerers.len()
    }

    fn average_techniques(&self) -> f64 {
        if self.database.sorcerers.is_empty() {
            0.0
        } else {
            let total: usize = self.database.sorcerers.iter()
                .map(|s| s.technique_count())
                .sum();
            total as f64 / self.database.sorcerers.len() as f64
        }
    }

    fn grade_distribution(&self) -> std::collections::HashMap<&'a str, usize> {
        let mut distribution = std::collections::HashMap::new();
        for sorcerer in &self.database.sorcerers {
            *distribution.entry(sorcerer.grade).or_insert(0) += 1;
        }
        distribution
    }

    fn generate_report(&self) -> String {
        let mut report = String::new();

        report.push_str("=== 呪術師データベース統計 ===\\n");
        report.push_str(&format!("総呪術師数: {}\\n", self.total_sorcerers()));
        report.push_str(&format!("平均技数: {:.1}\\n", self.average_techniques()));

        if let Some(most_skilled) = self.database.find_most_skilled() {
            report.push_str(&format!("最多技保有者: {} ({}技)\\n",
                most_skilled.name, most_skilled.technique_count()));
        }

        report.push_str("\\n等級分布:\\n");
        for (grade, count) in self.grade_distribution() {
            report.push_str(&format!("  {}: {}人\\n", grade, count));
        }

        report
    }
}

fn main() {
    // テスト用データ
    let names = ["五条悟", "両面宿儺", "虎杖悠仁", "伏黒恵", "釘崎野薔薇"];
    let grades = ["特級", "特級", "1級", "2級", "3級"];

    let mut database = SorcererDatabase::new();

    // 呪術師データの追加
    let mut gojo = SorcererRef::new(names[0], grades[0]);
    gojo.add_technique("無下限呪術");
    gojo.add_technique("術式順転『蒼』");
    gojo.add_technique("術式反転『赫』");
    gojo.add_technique("虚式『茈』");
    database.add_sorcerer(gojo);

    let mut sukuna = SorcererRef::new(names[1], grades[1]);
    sukuna.add_technique("解");
    sukuna.add_technique("捌");
    sukuna.add_technique("伏魔御廚子");
    database.add_sorcerer(sukuna);

    let mut yuji = SorcererRef::new(names[2], grades[2]);
    yuji.add_technique("黒閃");
    yuji.add_technique("発散");
    database.add_sorcerer(yuji);

    let mut megumi = SorcererRef::new(names[3], grades[3]);
    megumi.add_technique("十種影法術");
    megumi.add_technique("玉犬");
    database.add_sorcerer(megumi);

    let mut nobara = SorcererRef::new(names[4], grades[4]);
    nobara.add_technique("芻霊呪法");
    database.add_sorcerer(nobara);

    // データベース操作のテスト
    println!("=== 呪術師データベーステスト ===");

    // 名前検索
    if let Some(gojo) = database.find_by_name("五条悟") {
        println!("\\n五条悟の情報:");
        println!("  等級: {}", gojo.grade);
        println!("  技数: {}", gojo.technique_count());
        println!("  技: {:?}", gojo.techniques);
    }

    // 等級検索
    let special_grade = database.find_by_grade("特級");
    println!("\\n特級呪術師:");
    for sorcerer in special_grade {
        println!("  {}", sorcerer.name);
    }

    // 技検索
    let black_flash_users = database.find_by_technique("黒閃");
    println!("\\n黒閃使い:");
    for sorcerer in black_flash_users {
        println!("  {}", sorcerer.name);
    }

    // 統計情報
    let stats = database.generate_statistics();
    println!("\\n{}", stats.generate_report());
}

問題6: 複雑なライフタイム関係

複数の構造体間で参照を持つ複雑なシステムを実装せよ。

// 学校、クラス、生徒の関係を表現
// 学校は複数のクラスを持つ
// クラスは複数の生徒を持つ
// 生徒は所属クラスへの参照を持つ

struct School<'a> {
    name: &'a str,
    classes: Vec<Class<'a>>,
}

struct Class<'a> {
    name: &'a str,
    teacher: &'a str,
    students: Vec<Student<'a>>,
}

struct Student<'a> {
    name: &'a str,
    grade: i32,
    class: Option<&'a str>, // クラス名への参照
}

// 学校システムの管理機能を実装せよ
解答を見る
#[derive(Debug)]
struct School<'a> {
    name: &'a str,
    classes: Vec<Class<'a>>,
}

#[derive(Debug)]
struct Class<'a> {
    name: &'a str,
    teacher: &'a str,
    students: Vec<Student<'a>>,
}

#[derive(Debug)]
struct Student<'a> {
    name: &'a str,
    grade: i32,
    class: Option<&'a str>,
}

impl<'a> School<'a> {
    fn new(name: &'a str) -> Self {
        School {
            name,
            classes: Vec::new(),
        }
    }

    fn add_class(&mut self, class: Class<'a>) {
        self.classes.push(class);
    }

    fn find_class(&self, class_name: &str) -> Option<&Class<'a>> {
        self.classes.iter()
            .find(|class| class.name == class_name)
    }

    fn find_class_mut(&mut self, class_name: &str) -> Option<&mut Class<'a>> {
        self.classes.iter_mut()
            .find(|class| class.name == class_name)
    }

    fn find_student(&self, student_name: &str) -> Option<(&Class<'a>, &Student<'a>)> {
        for class in &self.classes {
            if let Some(student) = class.find_student(student_name) {
                return Some((class, student));
            }
        }
        None
    }

    fn get_all_students(&self) -> Vec<&Student<'a>> {
        self.classes.iter()
            .flat_map(|class| class.students.iter())
            .collect()
    }

    fn get_top_students(&self, min_grade: i32) -> Vec<&Student<'a>> {
        self.get_all_students().into_iter()
            .filter(|student| student.grade >= min_grade)
            .collect()
    }

    fn generate_school_report(&self) -> String {
        let mut report = String::new();

        report.push_str(&format!("=== {} 学校レポート ===\\n", self.name));
        report.push_str(&format!("クラス数: {}\\n", self.classes.len()));

        let total_students: usize = self.classes.iter()
            .map(|class| class.students.len())
            .sum();
        report.push_str(&format!("総生徒数: {}\\n", total_students));

        if total_students > 0 {
            let total_grade: i32 = self.get_all_students().iter()
                .map(|student| student.grade)
                .sum();
            let avg_grade = total_grade as f64 / total_students as f64;
            report.push_str(&format!("平均成績: {:.1}\\n", avg_grade));
        }

        report.push_str("\\nクラス別詳細:\\n");
        for class in &self.classes {
            report.push_str(&class.generate_class_summary());
        }

        report
    }
}

impl<'a> Class<'a> {
    fn new(name: &'a str, teacher: &'a str) -> Self {
        Class {
            name,
            teacher,
            students: Vec::new(),
        }
    }

    fn add_student(&mut self, mut student: Student<'a>) {
        student.class = Some(self.name);
        self.students.push(student);
    }

    fn find_student(&self, student_name: &str) -> Option<&Student<'a>> {
        self.students.iter()
            .find(|student| student.name == student_name)
    }

    fn get_average_grade(&self) -> f64 {
        if self.students.is_empty() {
            0.0
        } else {
            let total: i32 = self.students.iter()
                .map(|student| student.grade)
                .sum();
            total as f64 / self.students.len() as f64
        }
    }

    fn get_top_student(&self) -> Option<&Student<'a>> {
        self.students.iter()
            .max_by_key(|student| student.grade)
    }

    fn generate_class_summary(&self) -> String {
        let mut summary = String::new();

        summary.push_str(&format!("  {} (担任: {})\\n", self.name, self.teacher));
        summary.push_str(&format!("    生徒数: {}\\n", self.students.len()));
        summary.push_str(&format!("    平均成績: {:.1}\\n", self.get_average_grade()));

        if let Some(top_student) = self.get_top_student() {
            summary.push_str(&format!("    トップ: {} ({}点)\\n",
                top_student.name, top_student.grade));
        }

        summary
    }
}

impl<'a> Student<'a> {
    fn new(name: &'a str, grade: i32) -> Self {
        Student {
            name,
            grade,
            class: None,
        }
    }

    fn get_class_name(&self) -> &str {
        self.class.unwrap_or("未所属")
    }

    fn get_grade_level(&self) -> &str {
        match self.grade {
            90..=100 => "優秀",
            80..=89 => "良好",
            70..=79 => "普通",
            60..=69 => "要努力",
            _ => "要指導",
        }
    }
}

fn main() {
    // 学校データの作成
    let mut jujutsu_school = School::new("東京呪術高等専門学校");

    // 1年生クラス
    let mut first_year = Class::new("1年A組", "五条悟");
    first_year.add_student(Student::new("虎杖悠仁", 85));
    first_year.add_student(Student::new("伏黒恵", 92));
    first_year.add_student(Student::new("釘崎野薔薇", 88));

    // 2年生クラス
    let mut second_year = Class::new("2年A組", "夜蛾正道");
    second_year.add_student(Student::new("禪院真希", 94));
    second_year.add_student(Student::new("狗巻棘", 90));
    second_year.add_student(Student::new("パンダ", 78));

    jujutsu_school.add_class(first_year);
    jujutsu_school.add_class(second_year);

    // 学校システムのテスト
    println!("=== 呪術高専管理システム ===");

    // 学校レポート
    println!("{}", jujutsu_school.generate_school_report());

    // 生徒検索
    if let Some((class, student)) = jujutsu_school.find_student("虎杖悠仁") {
        println!("\\n生徒情報:");
        println!("  名前: {}", student.name);
        println!("  成績: {} ({})", student.grade, student.get_grade_level());
        println!("  クラス: {}", class.name);
        println!("  担任: {}", class.teacher);
    }

    // 優秀な生徒一覧
    let top_students = jujutsu_school.get_top_students(90);
    println!("\\n優秀な生徒 (90点以上):");
    for student in top_students {
        println!("  {} - {}点 ({})",
            student.name, student.grade, student.get_class_name());
    }

    // クラス別詳細
    println!("\\n=== クラス別詳細 ===");
    for class in &jujutsu_school.classes {
        println!("{}:", class.name);
        for student in &class.students {
            println!("  {} - {}点 ({})",
                student.name, student.grade, student.get_grade_level());
        }
        println!();
    }
}

総合問題

問題7: 呪術戦闘管理システム

これまで学んだ所有権、借用、ライフタイムのすべてを使って、複雑な戦闘管理システムを実装せよ。

要件:

  • 複数の戦闘同時管理
  • 戦闘参加者の管理
  • 技の使用履歴
  • リアルタイム統計
  • メモリ効率的な設計

自由に設計して実装してみよう!所有権、借用、ライフタイムを適切に使い分けること。

解答例を見る
use std::collections::HashMap;

// 戦闘参加者
#[derive(Debug, Clone)]
struct Combatant {
    name: String,
    hp: i32,
    max_hp: i32,
    power: i32,
    techniques: Vec<String>,
}

impl Combatant {
    fn new(name: &str, hp: i32, power: i32) -> Self {
        Combatant {
            name: String::from(name),
            hp,
            max_hp: hp,
            power,
            techniques: Vec::new(),
        }
    }

    fn add_technique(&mut self, technique: String) {
        self.techniques.push(technique);
    }

    fn is_alive(&self) -> bool {
        self.hp > 0
    }

    fn take_damage(&mut self, damage: i32) {
        self.hp = (self.hp - damage).max(0);
    }

    fn get_hp_percentage(&self) -> f64 {
        (self.hp as f64 / self.max_hp as f64) * 100.0
    }
}

// 戦闘ログエントリ
#[derive(Debug, Clone)]
struct LogEntry {
    round: u32,
    action: String,
    timestamp: u64,
}

// 個別戦闘
struct Battle<'a> {
    id: u32,
    name: String,
    combatants: Vec<&'a mut Combatant>,
    log: Vec<LogEntry>,
    current_round: u32,
    is_active: bool,
}

impl<'a> Battle<'a> {
    fn new(id: u32, name: String) -> Self {
        Battle {
            id,
            name,
            combatants: Vec::new(),
            log: Vec::new(),
            current_round: 1,
            is_active: false,
        }
    }

    fn add_combatant(&mut self, combatant: &'a mut Combatant) {
        self.combatants.push(combatant);
    }

    fn start_battle(&mut self) {
        self.is_active = true;
        self.log_action(format!("戦闘開始: {}", self.name));
    }

    fn log_action(&mut self, action: String) {
        let entry = LogEntry {
            round: self.current_round,
            action,
            timestamp: self.log.len() as u64,
        };
        self.log.push(entry);
    }

    fn execute_round(&mut self) -> bool {
        if !self.is_active || self.combatants.len() < 2 {
            return false;
        }

        self.log_action(format!("--- ラウンド {} ---", self.current_round));

        // 生存者のみで戦闘
        let alive_combatants: Vec<_> = self.combatants.iter()
            .filter(|c| c.is_alive())
            .collect();

        if alive_combatants.len() < 2 {
            self.end_battle();
            return false;
        }

        // 簡易的な戦闘ロジック
        for i in 0..alive_combatants.len() {
            if !alive_combatants[i].is_alive() {
                continue;
            }

            // 攻撃対象を選択(次の生存者)
            let target_idx = (i + 1) % alive_combatants.len();
            if target_idx != i && alive_combatants[target_idx].is_alive() {
                let attacker_name = &alive_combatants[i].name;
                let target_name = &alive_combatants[target_idx].name;
                let damage = alive_combatants[i].power / 10;

                // 実際のダメージ適用は元の参照を通して
                for combatant in &mut self.combatants {
                    if combatant.name == *target_name {
                        combatant.take_damage(damage);
                        break;
                    }
                }

                self.log_action(format!("{} が {} を攻撃! {}ダメージ",
                    attacker_name, target_name, damage));
            }
        }

        self.current_round += 1;
        true
    }

    fn end_battle(&mut self) {
        self.is_active = false;

        let winner = self.combatants.iter()
            .find(|c| c.is_alive())
            .map(|c| c.name.clone());

        if let Some(winner_name) = winner {
            self.log_action(format!("{} の勝利!", winner_name));
        } else {
            self.log_action("引き分け".to_string());
        }
    }

    fn get_battle_status(&self) -> BattleStatus {
        let alive_count = self.combatants.iter()
            .filter(|c| c.is_alive())
            .count();

        BattleStatus {
            battle_id: self.id,
            name: &self.name,
            is_active: self.is_active,
            current_round: self.current_round,
            alive_combatants: alive_count,
            total_combatants: self.combatants.len(),
            log_entries: self.log.len(),
        }
    }
}

// 戦闘状態の読み取り専用ビュー
#[derive(Debug)]
struct BattleStatus<'a> {
    battle_id: u32,
    name: &'a str,
    is_active: bool,
    current_round: u32,
    alive_combatants: usize,
    total_combatants: usize,
    log_entries: usize,
}

// 戦闘管理システム
struct BattleManager {
    combatants: HashMap<String, Combatant>,
    next_battle_id: u32,
}

impl BattleManager {
    fn new() -> Self {
        BattleManager {
            combatants: HashMap::new(),
            next_battle_id: 1,
        }
    }

    fn register_combatant(&mut self, combatant: Combatant) {
        self.combatants.insert(combatant.name.clone(), combatant);
    }

    fn create_battle(&mut self, name: String, participant_names: Vec<&str>) -> Option<Battle> {
        if participant_names.len() < 2 {
            return None;
        }

        let mut battle = Battle::new(self.next_battle_id, name);
        self.next_battle_id += 1;

        // 参加者の追加(借用で)
        for name in participant_names {
            if let Some(combatant) = self.combatants.get_mut(name) {
                // この部分は実際のライフタイム制約により複雑になる
                // 実用的には別のアプローチが必要
            }
        }

        Some(battle)
    }

    fn get_combatant(&self, name: &str) -> Option<&Combatant> {
        self.combatants.get(name)
    }

    fn get_combatant_mut(&mut self, name: &str) -> Option<&mut Combatant> {
        self.combatants.get_mut(name)
    }

    fn generate_overall_stats(&self) -> String {
        let mut stats = String::new();

        stats.push_str("=== 全体統計 ===\\n");
        stats.push_str(&format!("登録戦闘者数: {}\\n", self.combatants.len()));

        let alive_count = self.combatants.values()
            .filter(|c| c.is_alive())
            .count();
        stats.push_str(&format!("生存者数: {}\\n", alive_count));

        if let Some(strongest) = self.combatants.values()
            .filter(|c| c.is_alive())
            .max_by_key(|c| c.power) {
            stats.push_str(&format!("最強戦闘者: {} (呪力: {})\\n",
                strongest.name, strongest.power));
        }

        stats.push_str("\\n=== 戦闘者一覧 ===\\n");
        for combatant in self.combatants.values() {
            let status = if combatant.is_alive() { "生存" } else { "戦闘不能" };
            stats.push_str(&format!("{}: HP {}/{} ({}%) - {}\\n",
                combatant.name,
                combatant.hp,
                combatant.max_hp,
                combatant.get_hp_percentage(),
                status
            ));
        }

        stats
    }
}

fn main() {
    println!("=== 呪術戦闘管理システム ===");

    let mut manager = BattleManager::new();

    // 戦闘者の登録
    let mut gojo = Combatant::new("五条悟", 2000, 1800);
    gojo.add_technique("無下限呪術".to_string());
    gojo.add_technique("術式順転『蒼』".to_string());

    let mut sukuna = Combatant::new("両面宿儺", 1800, 1700);
    sukuna.add_technique("解".to_string());
    sukuna.add_technique("捌".to_string());

    let mut yuji = Combatant::new("虎杖悠仁", 1200, 900);
    yuji.add_technique("黒閃".to_string());

    let mut megumi = Combatant::new("伏黒恵", 1000, 800);
    megumi.add_technique("十種影法術".to_string());

    manager.register_combatant(gojo);
    manager.register_combatant(sukuna);
    manager.register_combatant(yuji);
    manager.register_combatant(megumi);

    // 初期状態表示
    println!("{}", manager.generate_overall_stats());

    // 模擬戦闘(簡易版)
    println!("\\n=== 模擬戦闘実行 ===");

    // 五条 vs 宿儺
    if let Some(gojo) = manager.get_combatant_mut("五条悟") {
        gojo.take_damage(300);
    }

    if let Some(sukuna) = manager.get_combatant_mut("両面宿儺") {
        sukuna.take_damage(400);
    }

    println!("戦闘後の状態:");
    if let Some(gojo) = manager.get_combatant("五条悟") {
        println!("五条悟: HP {}/2000 ({:.1}%)",
            gojo.hp, gojo.get_hp_percentage());
    }

    if let Some(sukuna) = manager.get_combatant("両面宿儺") {
        println!("両面宿儺: HP {}/1800 ({:.1}%)",
            sukuna.hp, sukuna.get_hp_percentage());
    }

    // 最終統計
    println!("\\n{}", manager.generate_overall_stats());
}

章末総括

第2章の練習問題、お疲れ様!所有権・借用・ライフタイムという三位一体の力を実践で確認できたな。

これらの概念を通して学んだこと:

  • 所有権 - メモリ安全性の基盤
  • 借用 - 効率的なデータ共有
  • ライフタイム - 参照の生存期間管理
  • コンパイラとの対話 - エラーメッセージから学ぶ

五条先生の最終メッセージ

Rustの所有権システムは最初は複雑に感じるかもしれない。でも一度マスターすれば、他の言語では味わえない安全性と性能を手に入れられる。

俺の無下限術式と同じで、理解すれば絶対的な力になる。諦めずに練習を続けろ。

次は第3章「反転術式編」でエラーハンドリングを学ぼう。エラーを逆手に取る技術だ。


「所有権を極めれば、メモリの支配者になれる」