課題9: 関数とクロージャの実践
マンダトリー要件
問題1:関数の実装(20点)
様々な関数を実装しなさい。
// 数値の範囲チェック
fn in_range(value: i32, min: i32, max: i32) -> bool {
// TODO: 実装
}
// 文字列の最初のn文字
fn first_n_chars(s: &str, n: usize) -> &str {
// TODO: 実装
}
// 配列の要素を交換
fn swap_elements(arr: &mut [i32], i: usize, j: usize) {
// TODO: 実装
}
// 文字列を逆順にする
fn reverse_string(s: &str) -> String {
// TODO: 実装
}
// 2つの配列を結合
fn concat_arrays(a: &[i32], b: &[i32]) -> Vec<i32> {
// TODO: 実装
}
// 最大公約数(ユークリッドの互除法)
fn gcd(a: u32, b: u32) -> u32 {
// TODO: 実装
}
// 最小公倍数
fn lcm(a: u32, b: u32) -> u32 {
// TODO: 実装(gcdを使用)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_in_range() {
assert!(in_range(5, 1, 10));
assert!(!in_range(15, 1, 10));
}
#[test]
fn test_first_n_chars() {
assert_eq!(first_n_chars("hello", 3), "hel");
assert_eq!(first_n_chars("hi", 5), "hi");
}
#[test]
fn test_swap_elements() {
let mut arr = [1, 2, 3, 4, 5];
swap_elements(&mut arr, 0, 4);
assert_eq!(arr, [5, 2, 3, 4, 1]);
}
#[test]
fn test_reverse_string() {
assert_eq!(reverse_string("hello"), "olleh");
}
#[test]
fn test_concat_arrays() {
assert_eq!(concat_arrays(&[1, 2], &[3, 4]), vec![1, 2, 3, 4]);
}
#[test]
fn test_gcd() {
assert_eq!(gcd(48, 18), 6);
assert_eq!(gcd(100, 50), 50);
}
#[test]
fn test_lcm() {
assert_eq!(lcm(4, 6), 12);
assert_eq!(lcm(21, 6), 42);
}
}
提出物:problem1_functions.rs
問題2:クロージャの基本(20点)
クロージャを活用した処理を実装しなさい。
// クロージャを使った配列の変換
fn transform_array<F>(arr: &[i32], f: F) -> Vec<i32>
where
F: Fn(i32) -> i32,
{
// TODO: 実装
}
// クロージャを使ったフィルタリング
fn filter_array<F>(arr: &[i32], predicate: F) -> Vec<i32>
where
F: Fn(&i32) -> bool,
{
// TODO: 実装
}
// クロージャを使った累積計算
fn fold_array<F>(arr: &[i32], init: i32, f: F) -> i32
where
F: Fn(i32, i32) -> i32,
{
// TODO: 実装
}
// カウンターを返すクロージャ
fn make_counter() -> impl FnMut() -> i32 {
// TODO: 実装
// 呼ばれるたびに1ずつ増える値を返す
}
// 指定された値を加算するクロージャを返す
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
// TODO: 実装
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transform_array() {
let arr = [1, 2, 3, 4, 5];
let result = transform_array(&arr, |x| x * 2);
assert_eq!(result, vec![2, 4, 6, 8, 10]);
}
#[test]
fn test_filter_array() {
let arr = [1, 2, 3, 4, 5, 6];
let result = filter_array(&arr, |&x| x % 2 == 0);
assert_eq!(result, vec![2, 4, 6]);
}
#[test]
fn test_fold_array() {
let arr = [1, 2, 3, 4, 5];
let result = fold_array(&arr, 0, |acc, x| acc + x);
assert_eq!(result, 15);
}
#[test]
fn test_make_counter() {
let mut counter = make_counter();
assert_eq!(counter(), 1);
assert_eq!(counter(), 2);
assert_eq!(counter(), 3);
}
#[test]
fn test_make_adder() {
let add_5 = make_adder(5);
assert_eq!(add_5(10), 15);
assert_eq!(add_5(20), 25);
}
}
提出物:problem2_closures.rs
問題3:イテレータとクロージャ(20点)
イテレータメソッドとクロージャを組み合わせた処理を実装しなさい。
// 偶数のみを2倍にして合計
fn sum_doubled_evens(numbers: &[i32]) -> i32 {
// TODO: iter()、filter()、map()、sum()を使用
}
// 文字列のリストから長さ5以上のものを大文字に変換
fn uppercase_long_strings(strings: &[&str]) -> Vec<String> {
// TODO: 実装
}
// 数値のリストから最初の5つの平方数を取得
fn first_n_squares(n: usize) -> Vec<i32> {
// TODO: (0..).map().take().collect()を使用
}
// ネストされた配列を平坦化
fn flatten(nested: Vec<Vec<i32>>) -> Vec<i32> {
// TODO: flat_map()を使用
}
// 重複を削除してソート
fn unique_sorted(numbers: Vec<i32>) -> Vec<i32> {
// TODO: 実装
}
// 辞書順でソートして結合
fn join_sorted(words: &[&str]) -> String {
// TODO: 実装(", "で結合)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_doubled_evens() {
assert_eq!(sum_doubled_evens(&[1, 2, 3, 4, 5, 6]), 24);
}
#[test]
fn test_uppercase_long_strings() {
let words = vec!["hello", "hi", "world", "rust"];
assert_eq!(
uppercase_long_strings(&words),
vec!["HELLO", "WORLD"]
);
}
#[test]
fn test_first_n_squares() {
assert_eq!(first_n_squares(5), vec![0, 1, 4, 9, 16]);
}
#[test]
fn test_flatten() {
let nested = vec![vec![1, 2], vec![3, 4], vec![5]];
assert_eq!(flatten(nested), vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_unique_sorted() {
assert_eq!(unique_sorted(vec![3, 1, 4, 1, 5, 9, 2, 6, 5]),
vec![1, 2, 3, 4, 5, 6, 9]);
}
#[test]
fn test_join_sorted() {
assert_eq!(join_sorted(&["rust", "is", "awesome"]),
"awesome, is, rust");
}
}
提出物:problem3_iterators.rs
問題4:高階関数の実装(20点)
高階関数を実装しなさい。
// 関数を2回適用
fn apply_twice<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
// TODO: 実装
}
// 関数の合成
fn compose<F, G>(f: F, g: G) -> impl Fn(i32) -> i32
where
F: Fn(i32) -> i32,
G: Fn(i32) -> i32,
{
// TODO: 実装
// f(g(x))を返す
}
// 条件に応じた関数の選択
fn conditional_apply<F, G>(
condition: bool,
f: F,
g: G,
x: i32,
) -> i32
where
F: Fn(i32) -> i32,
G: Fn(i32) -> i32,
{
// TODO: 実装
}
// リトライ機能付き関数実行
fn retry<F>(f: F, max_attempts: u32) -> Result<i32, &'static str>
where
F: Fn() -> Result<i32, &'static str>,
{
// TODO: 実装
// max_attempts回までリトライ
}
// メモ化関数
fn memoize<F>(f: F) -> impl FnMut(i32) -> i32
where
F: Fn(i32) -> i32,
{
// TODO: 実装
// HashMap を使ってキャッシュ
use std::collections::HashMap;
let mut cache = HashMap::new();
move |x| {
// TODO: キャッシュから取得、なければ計算
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_twice() {
let double = |x| x * 2;
assert_eq!(apply_twice(double, 5), 20);
}
#[test]
fn test_compose() {
let add_one = |x| x + 1;
let double = |x| x * 2;
let composed = compose(double, add_one);
assert_eq!(composed(5), 12); // double(add_one(5)) = double(6) = 12
}
#[test]
fn test_conditional_apply() {
let double = |x| x * 2;
let triple = |x| x * 3;
assert_eq!(conditional_apply(true, double, triple, 5), 10);
assert_eq!(conditional_apply(false, double, triple, 5), 15);
}
}
提出物:problem4_higher_order.rs
---
ボーナス課題
> ボーナス: 以下はオプションです。マンダトリー要件を完了してから挑戦してください。
ボーナス1:関数型プログラミング演習(5点)
関数型スタイルで実装しなさい。
// パイプライン処理
fn pipeline() -> i32 {
// TODO: 実装
// 1から10までの数値で:
// 1. 偶数のみ
// 2. 2乗
// 3. 合計
}
// カリー化された加算
fn curried_add(a: i32) -> impl Fn(i32) -> impl Fn(i32) -> i32 {
// TODO: 実装
// curried_add(1)(2)(3) = 6
}
// 部分適用
fn partial_apply<F>(f: F, a: i32) -> impl Fn(i32) -> i32
where
F: Fn(i32, i32) -> i32,
{
// TODO: 実装
}
提出物:bonus1_functional/プロジェクト
ボーナス2:イテレータの実装(5点)
カスタムイテレータを実装しなさい。
// フィボナッチ数列のイテレータ
struct Fibonacci {
curr: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
// TODO: 実装
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
// TODO: 実装
}
}
// 無限に素数を生成するイテレータ
struct Primes {
current: u64,
}
impl Primes {
fn new() -> Self {
// TODO: 実装
}
fn is_prime(n: u64) -> bool {
// TODO: 実装
}
}
impl Iterator for Primes {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
// TODO: 実装
}
}
提出物:bonus2_custom_iterators/プロジェクト
ボーナス3:並列処理(5点)
Rayon クレートを使った並列処理を実装しなさい。
use rayon::prelude::*;
// 並列でフィルタリングとマッピング
fn parallel_process(numbers: Vec<i32>) -> Vec<i32> {
// TODO: 実装
// 偶数のみ、2乗、ソート
}
// 並列で合計計算
fn parallel_sum(numbers: &[i32]) -> i32 {
// TODO: 実装
}
// 並列で最大値を見つける
fn parallel_max(numbers: &[i32]) -> Option<i32> {
// TODO: 実装
}
提出物:bonus3_parallel/プロジェクト
ボーナス4:遅延評価(5点)
遅延評価を活用した処理を実装しなさい。
// 大きな数値列を効率的に処理
fn lazy_evaluation() {
// TODO: 実装
// 1億までの数値から、
// 1000番目の素数を見つける
}
// 無限イテレータの活用
fn infinite_iterator_demo() {
// TODO: 実装
// 無限に続くフィボナッチ数列から
// 1000を超える最初の数を見つける
}
提出物:bonus4_lazy_evaluation/プロジェクト
ボーナス5:関数の組み合わせライブラリ(5点)
関数の組み合わせを簡単にするライブラリを実装しなさい。
// パイプ演算子的な機能
trait Pipe: Sized {
fn pipe<F, R>(self, f: F) -> R
where
F: FnOnce(Self) -> R,
{
f(self)
}
}
impl<T> Pipe for T {}
// タップ(デバッグ用)
trait Tap: Sized {
fn tap<F>(self, f: F) -> Self
where
F: FnOnce(&Self),
{
f(&self);
self
}
}
impl<T> Tap for T {}
fn main() {
let result = 5
.pipe(|x| x * 2)
.tap(|x| println!("中間結果: {}", x))
.pipe(|x| x + 3);
println!("最終結果: {}", result);
}
提出物:bonus5_combinator_library/プロジェクト
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:関数実装 | 20点 | 基本的な関数の実装 |
| 問題2:クロージャ | 20点 | クロージャの理解 |
| 問題3:イテレータ | 20点 | イテレータの活用 |
| 問題4:高階関数 | 20点 | 高階関数の実装 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:関数型 | 5点 | 関数型スタイル |
| ボーナス2:イテレータ | 5点 | カスタム実装 |
| ボーナス3:並列処理 | 5点 | Rayonの活用 |
| ボーナス4:遅延評価 | 5点 | 効率的な処理 |
| ボーナス5:コンビネータ | 5点 | ライブラリ設計 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-09/
├── problem1_functions.rs
├── problem2_closures.rs
├── problem3_iterators.rs
├── problem4_higher_order.rs
└── bonus/
├── bonus1_functional/
├── bonus2_custom_iterators/
├── bonus3_parallel/
├── bonus4_lazy_evaluation/
└── bonus5_combinator_library/
提出期限
- マンダトリー:第9章学習後、1週間以内
- ボーナス:第10章修了時まで
---
ヒント
問題1のヒント
gcd実装:
fn gcd(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
let temp = b;
b = a % b;
a = temp;
}
a
}
問題2のヒント
make_counter実装:
fn make_counter() -> impl FnMut() -> i32 {
let mut count = 0;
move || {
count += 1;
count
}
}
問題3のヒント
sum_doubled_evens実装:
fn sum_doubled_evens(numbers: &[i32]) -> i32 {
numbers
.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.sum()
}
問題4のヒント
compose実装:
fn compose<F, G>(f: F, g: G) -> impl Fn(i32) -> i32
where
F: Fn(i32) -> i32,
G: Fn(i32) -> i32,
{
move |x| f(g(x))
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
- [ ] 関数の定義と呼び出し
- [ ] クロージャの構文
- [ ] 環境のキャプチャ(不変借用、可変借用、move)
- [ ] Fn、FnMut、FnOnce の違い
- [ ] イテレータメソッド(map、filter、fold)
- [ ] メソッドチェーン
- [ ] 高階関数の実装
- [ ] ゼロコスト抽象化
次の章では、構造体を学びます。データと振る舞いを組み合わせた構造体の定義とメソッド実装について理解を深めます。