課題13: イテレータ (Iterators)
マンダトリー要件
問題1: イテレータの基本(20点)
以下のプログラムを完成させなさい。
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// TODO: イテレータを使って以下を実装
// 1. 偶数のみをフィルタリング (5点)
let evens: Vec<i32> = // ここを実装
// 2. 各要素を2乗 (5点)
let squared: Vec<i32> = // ここを実装
// 3. 合計を計算 (5点)
let sum: i32 = // ここを実装
// 4. 最初の3つの奇数を取得 (5点)
let first_three_odds: Vec<i32> = // ここを実装
println!("Evens: {:?}", evens);
println!("Squared: {:?}", squared);
println!("Sum: {}", sum);
println!("First three odds: {:?}", first_three_odds);
}
期待される出力:
Evens: [2, 4, 6, 8, 10]
Squared: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Sum: 55
First three odds: [1, 3, 5]
---
問題2: カスタムイテレータ - Counter (30点)
以下の仕様に従ってCounterイテレータを実装しなさい。
// 指定された開始値から終了値まで、ステップ値ずつ増加するイテレータ
struct Counter {
current: i32,
end: i32,
step: i32,
}
impl Counter {
fn new(start: i32, end: i32, step: i32) -> Self {
// TODO: 実装 (5点)
}
}
impl Iterator for Counter {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
// TODO: 実装 (10点)
}
}
// ボーナス: size_hintを実装 (5点)
impl Counter {
fn size_hint(&self) -> (usize, Option<usize>) {
// TODO: 実装
}
}
fn main() {
// テストケース1: 通常のカウント
let counter = Counter::new(0, 10, 2);
let result: Vec<i32> = counter.collect();
assert_eq!(result, vec![0, 2, 4, 6, 8]);
// テストケース2: 負のステップ
let counter = Counter::new(10, 0, -2);
let result: Vec<i32> = counter.collect();
assert_eq!(result, vec![10, 8, 6, 4, 2]);
// テストケース3: ステップが大きい場合
let counter = Counter::new(0, 100, 30);
let result: Vec<i32> = counter.collect();
assert_eq!(result, vec![0, 30, 60, 90]);
println!("All tests passed!");
}
評価基準:
newの実装: 5点nextの基本実装: 10点- エッジケースの処理(負のステップ等): 10点
size_hintの実装: 5点
---
問題3: データ処理パイプライン (30点)
以下のPerson構造体を使って、データ処理パイプラインを実装しなさい。
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
salary: u32,
department: String,
}
fn main() {
let people = vec![
Person { name: "Alice".to_string(), age: 25, salary: 50000, department: "Engineering".to_string() },
Person { name: "Bob".to_string(), age: 30, salary: 60000, department: "Sales".to_string() },
Person { name: "Charlie".to_string(), age: 35, salary: 70000, department: "Engineering".to_string() },
Person { name: "David".to_string(), age: 28, salary: 55000, department: "HR".to_string() },
Person { name: "Eve".to_string(), age: 32, salary: 65000, department: "Engineering".to_string() },
];
// TODO: 以下の関数を実装
// 1. Engineering部門の平均給与を計算 (10点)
let avg_salary = calculate_avg_salary(&people, "Engineering");
println!("Average Engineering salary: {}", avg_salary);
// 2. 年齢が30歳以上の人の名前を取得(給与の降順) (10点)
let senior_members = get_senior_members_sorted(&people);
println!("Senior members: {:?}", senior_members);
// 3. 部門ごとの人数をカウント (10点)
let dept_count = count_by_department(&people);
println!("Department count: {:?}", dept_count);
}
fn calculate_avg_salary(people: &[Person], dept: &str) -> f64 {
// TODO: 実装
}
fn get_senior_members_sorted(people: &[Person]) -> Vec<String> {
// TODO: 実装(年齢30歳以上、給与降順)
}
fn count_by_department(people: &[Person]) -> std::collections::HashMap<String, usize> {
// TODO: 実装
}
期待される出力:
Average Engineering salary: 61666.67
Senior members: ["Charlie", "Eve", "Bob"]
Department count: {"Engineering": 3, "Sales": 1, "HR": 1}
---
ボーナス課題
ボーナス1: フィボナッチイテレータ (10点)
無限のフィボナッチ数列を生成するイテレータを実装しなさい。
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: 実装
}
}
fn main() {
let fib = Fibonacci::new();
// 最初の10個
let first_10: Vec<u64> = fib.take(10).collect();
assert_eq!(first_10, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
// 1000以下のフィボナッチ数
let fib = Fibonacci::new();
let under_1000: Vec<u64> = fib.take_while(|&x| x < 1000).collect();
println!("Fibonacci numbers under 1000: {:?}", under_1000);
// 偶数のフィボナッチ数の最初の5個
let fib = Fibonacci::new();
let even_fib: Vec<u64> = fib.filter(|x| x % 2 == 0).take(5).collect();
println!("First 5 even Fibonacci: {:?}", even_fib);
}
評価基準:
- 正しいフィボナッチ数列の生成: 5点
- オーバーフロー処理: 3点
- コードの簡潔性: 2点
---
ボーナス2: CSV パーサー (10点)
CSVデータをパースするイテレータを実装しなさい。
struct CsvParser<'a> {
data: &'a str,
current_line: usize,
}
impl<'a> CsvParser<'a> {
fn new(data: &'a str) -> Self {
// TODO: 実装
}
}
impl<'a> Iterator for CsvParser<'a> {
type Item = Vec<&'a str>;
fn next(&mut self) -> Option<Self::Item> {
// TODO: 実装
// ヘッダー行はスキップする
}
}
fn main() {
let csv_data = "Name,Age,City\n\
Alice,25,Tokyo\n\
Bob,30,Osaka\n\
Charlie,35,Kyoto";
let parser = CsvParser::new(csv_data);
for row in parser {
println!("{:?}", row);
}
// 期待される出力:
// ["Alice", "25", "Tokyo"]
// ["Bob", "30", "Osaka"]
// ["Charlie", "35", "Kyoto"]
}
評価基準:
- 基本的なパース機能: 5点
- ヘッダースキップ: 2点
- エラーハンドリング(空行等): 3点
---
ボーナス3: チェーン可能なイテレータアダプタ (10点)
独自のイテレータアダプタを実装しなさい。
// WindowsIterator: 指定されたサイズのスライディングウィンドウを返す
struct Windows<I: Iterator> {
iter: I,
window_size: usize,
buffer: Vec<I::Item>,
}
impl<I: Iterator> Windows<I>
where
I::Item: Clone,
{
fn new(iter: I, window_size: usize) -> Self {
// TODO: 実装
}
}
impl<I: Iterator> Iterator for Windows<I>
where
I::Item: Clone,
{
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
// TODO: 実装
// バッファをスライディングウィンドウとして使う
}
}
// 拡張トレイトを定義
trait IteratorExt: Iterator {
fn windows(self, size: usize) -> Windows<Self>
where
Self: Sized,
Self::Item: Clone,
{
Windows::new(self, size)
}
}
// すべてのイテレータに実装
impl<I: Iterator> IteratorExt for I {}
fn main() {
let v = vec![1, 2, 3, 4, 5];
let windows: Vec<Vec<i32>> = v.iter()
.copied()
.windows(3)
.collect();
println!("{:?}", windows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
// 応用: 移動平均
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let moving_avg: Vec<f64> = data.iter()
.copied()
.windows(3)
.map(|w| w.iter().sum::<f64>() / w.len() as f64)
.collect();
println!("Moving average: {:?}", moving_avg);
// [2.0, 3.0, 4.0, 5.0]
}
評価基準:
Windowsの基本実装: 5点- スライディングウィンドウのロジック: 3点
- 拡張トレイトの実装: 2点
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:基本操作 | 20点 | イテレータメソッドの正しい使用 |
| 問題2:カスタムイテレータ | 30点 | Iteratorトレイトの実装 |
| 問題3:データ処理 | 30点 | 実践的なパイプライン構築 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:フィボナッチ | 10点 | 無限イテレータの実装 |
| ボーナス2:CSVパーサー | 10点 | 実用的なイテレータ |
| ボーナス3:カスタムアダプタ | 10点 | 高度なイテレータ技術 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-13/
├── src/
│ ├── problem1.rs
│ ├── problem2.rs
│ ├── problem3.rs
│ ├── bonus1.rs # オプション
│ ├── bonus2.rs # オプション
│ └── bonus3.rs # オプション
├── Cargo.toml
└── README.md
テスト
cargo test --release
提出期限
---
ヒント
問題1のヒント
// filter, map, sum, take を組み合わせる
numbers.iter().filter(|&x| x % 2 == 0).copied().collect()
問題2のヒント
// 負のステップの場合は逆向きに進む
if self.step > 0 {
if self.current < self.end {
// ...
}
} else {
if self.current > self.end {
// ...
}
}
問題3のヒント
// 平均値の計算
let sum: u32 = people.iter()
.filter(|p| p.department == dept)
.map(|p| p.salary)
.sum();
let count = people.iter()
.filter(|p| p.department == dept)
.count();
sum as f64 / count as f64
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
次の章では、クロージャについて学びます。イテレータと組み合わせることで、さらに強力な表現が可能になります。