learning-rust/part7/vector练习题.md

1234 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Rust Vector 练习题
> 建议先手动写出每道题的答案,再运行代码验证。
## 目录
- [一、概念辨析题](#一概念辨析题)
- [二、填空题:补充代码](#二填空题补充代码)
- [三、找出并修复错误](#三找出并修复错误)
- [四、编程题](#四编程题)
- [五、综合思考题](#五综合思考题)
- [参考答案](#参考答案)
---
## 一、概念辨析题
判断以下说法是否正确,并简要说明原因。
### 题目 1-1
> `Vec<T>` 只能存储相同类型的数据。
### 题目 1-2
> 从空 Vec 中 `pop()` 会引发 panic。
### 题目 1-3
> 以下两种创建 Vec 的方式完全等价,没有性能差异:
> ```rust
> let v1 = Vec::new();
> let v2 = vec![];
> ```
### 题目 1-4
> 使用索引 `v[3]` 访问 Vec 元素和使用 `v.get(3)` 的行为完全一致。
### 题目 1-5
> 对 Vec 进行 `for` 循环遍历会消耗 Vec 的所有权,遍历后 Vec 不再可用。
### 题目 1-6
> 当 Vec 的容量满了再 `push` 新元素时Rust 会自动重新分配一块更大的内存并将原数据拷贝过去。
### 题目 1-7
> `Vec::with_capacity(n)` 创建的 Vec 的长度也为 `n`。
### 题目 1-8
> 可以在遍历 Vec 的同时向其中添加元素。
---
## 二、填空题:补充代码
补全下列代码使其能通过编译并达到期望输出。
### 题目 2-1基本创建和操作
```rust
fn main() {
// 填空:创建一个包含元素 1, 2, 3, 4, 5 的 Vec
let mut v = ________;
// 填空:向末尾添加元素 6
v.________(6);
// 填空:移除并返回最后一个元素
let last = v.________();
println!("移除的元素是: {}", last);
// 填空:获取当前长度
println!("当前长度: {}", ________);
// 填空:获取指定位置元素的值(安全方式)
match v.________(2) {
Some(value) => println!("索引 2 的值是: {}", value),
None => println!("没有该元素"),
}
}
// 期望输出:
// 移除的元素是: 6
// 当前长度: 5
// 索引 2 的值是: 3
```
### 题目 2-2遍历 Vec
```rust
fn main() {
let v = vec![10, 20, 30, 40, 50];
// 填空:不可变遍历,打印每个元素
for ________ in ________ {
print!("{} ", i);
}
println!();
// 填空:可变遍历,每个元素翻倍
let mut v2 = vec![1, 2, 3, 4, 5];
for ________ in ________ {
*i ________ 2;
}
println!("翻倍后: {:?}", v2);
}
// 期望输出:
// 10 20 30 40 50
// 翻倍后: [2, 4, 6, 8, 10]
```
### 题目 2-3插入和删除
```rust
fn main() {
let mut v = vec![1, 2, 4, 5];
// 填空:在索引 2 处插入元素 3
v.________(2, 3);
// 填空:删除索引 4 处的元素
v.________(4);
// 填空:删除索引 0 处的元素
v.________(0);
println!("结果: {:?}", v);
}
// 期望输出:结果: [2, 3, 4]
```
### 题目 2-4枚举实现不同类型存储
```rust
// 填空:定义一个枚举,包含整数、浮点数、文本三种变体
________ Value {
Integer(i32),
Float(f64),
________(String),
}
fn main() {
// 填空:使用宏创建一个包含不同变体的 Vec
let row = ________;
Value::Integer(3),
Value::Float(3.14),
Value::Text(String::from("Hello")),
];
for v in &row {
match v {
Value::Integer(n) => print!("Int:{} ", n),
Value::Float(f) => print!("Float:{} ", f),
Value::Text(s) => print!("Text:{} ", s),
}
}
}
// 期望输出Int:3 Float:3.14 Text:Hello
```
### 题目 2-5切片操作
```rust
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
// 填空:获取 v[1..4] 的切片
let slice1 = ________;
println!("slice1: {:?}", slice1);
// 填空:获取前 3 个元素的切片
let slice2 = ________;
println!("slice2: {:?}", slice2);
// 填空:获取从索引 5 到末尾的切片
let slice3 = ________;
println!("slice3: {:?}", slice3);
}
// 期望输出:
// slice1: [2, 3, 4]
// slice2: [1, 2, 3]
// slice3: [6, 7, 8]
```
### 题目 2-6容量管理
```rust
fn main() {
// 填空:预分配容量为 100 的空 Vec
let mut v: Vec<i32> = Vec::________(100);
println!("创建后 - 长度: {},容量: {}", v.len(), v.capacity());
for i in 0..50 {
v.push(i);
}
println!("添加 50 个元素后 - 长度: {},容量: {}", v.len(), v.capacity());
// 填空:收缩容量以匹配长度(使用合适的方法)
v.________();
println!("收缩后 - 长度: {},容量: {}", v.len(), v.capacity());
}
```
### 题目 2-7常用方法组合
```rust
fn main() {
let mut v = vec![5, 2, 8, 1, 9, 3];
// 填空:排序(升序)
v.________();
// 填空:检查是否包含某个值
let has_five = v.________(&5);
// 填空:反转顺序
v.________();
println!("排序并反转后: {:?}", v);
println!("是否包含 5: {}", has_five);
}
// 期望输出:
// 排序并反转后: [9, 8, 5, 3, 2, 1]
// 是否包含 5: true
```
### 题目 2-8extend 和 drain
```rust
fn main() {
let mut v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
// 填空:将 v2 的所有元素追加到 v1
v1.________(&v2);
println!("追加后: {:?}", v1);
let mut v = vec![10, 20, 30, 40, 50];
// 填空:移除索引 1..4 的元素,返回被移除元素的迭代器
let drained: Vec<_> = v.________(1..4).collect();
println!("被移除的: {:?}", drained);
println!("剩余的: {:?}", v);
}
// 期望输出:
// 追加后: [1, 2, 3, 4, 5, 6]
// 被移除的: [20, 30, 40]
// 剩余的: [10, 50]
```
### 题目 2-9迭代器适配器
```rust
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 填空:过滤出偶数,映射为平方,收集到新 Vec
let result: Vec<i32> = v.________()
.________(|x| x % 2 == 0)
.________(|x| x * x)
.collect();
println!("偶数的平方: {:?}", result);
// 填空:求和
let sum: i32 = v.iter().________();
println!("总和: {}", sum);
}
// 期望输出:
// 偶数的平方: [4, 16, 36, 64, 100]
// 总和: 55
```
---
## 三、找出并修复错误
以下每段代码都有编译错误,请指出错误并写出修正后的代码。
### 题目 3-1
```rust
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // 不可变借用
v.push(4); // 可变借用
println!("{}", first); // 使用不可变借用
}
```
### 题目 3-2
```rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third = v[10];
println!("{}", third);
}
```
### 题目 3-3
```rust
fn main() {
let mut v = Vec::new();
v.push("hello");
v.push(42);
println!("{:?}", v);
}
```
### 题目 3-4
```rust
fn main() {
let mut v = vec![1, 2, 3];
for i in v.iter_mut() {
v.push(4); // 同时修改
}
println!("{:?}", v);
}
```
### 题目 3-5
```rust
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
for i in 0..v.len() {
if v[i] % 2 == 0 {
v.remove(i);
}
}
println!("{:?}", v);
}
```
### 题目 3-6
```rust
fn main() {
let v = vec![String::from("hello")];
let s = &v[0]; // 借用了 v 中的元素
drop(v); // 显式释放 v
println!("{}", s); // 使用了 s
}
```
### 题目 3-7
```rust
fn main() {
let v = vec![1, 2, 3];
// 想要获得 [1, 4, 9]
let squared: Vec<i32> = v.iter().map(|x| x * x);
println!("{:?}", squared);
}
```
### 题目 3-8
```rust
fn main() {
let mut v: Vec<u32> = Vec::new();
v.push(1);
let popped = v.pop();
println!("{:?}", popped); // 期望打印 1
let popped2 = v.pop(); // Vec 已空
println!("{}", popped2); // 这行会怎样?
}
```
### 题目 3-9
```rust
fn main() {
let v = vec![1, 2, 3, 4, 5];
for item in v {
println!("{}", item);
}
println!("v 还有 {} 个元素", v.len()); // 再次使用 v
}
```
### 题目 3-10
```rust
fn main() {
let mut v = vec![1, 2, 3];
let slice = &mut v[0..2]; // 可变借用切片
v.push(4); // 尝试修改原 Vec
slice[0] = 10;
println!("{:?}", v);
}
```
---
## 四、编程题
请在 `main.rs` 中编写以下程序,并实际运行验证。
### 题目 4-1成绩统计分析器
编写一个程序,完成以下功能:
1. 创建一个包含 10 个学生成绩的 `Vec<i32>`(成绩范围 0~100
2. 计算并打印:最高分、最低分、平均分
3. 找出所有不及格(< 60的成绩存入新 Vec 并打印
4. 判断是否有满分100 打印结果
5. 将所有成绩从高到低排序后打印
**期望输出示例(成绩不同则输出不同):**
```
成绩列表: [85, 42, 96, 78, 55, 88, 91, 63, 100, 74]
最高分: 100
最低分: 42
平均分: 77.2
不及格成绩: [42, 55]
有满分: 是
排序后(高到低): [100, 96, 91, 88, 85, 78, 74, 63, 55, 42]
```
### 题目 4-2斐波那契数列生成器
编写一个函数 `fibonacci(n: usize) -> Vec<u64>`接收项数 `n`返回一个包含前 `n` 项斐波那契数列的 Vec
- n = 0 时返回空 Vec
- n = 1 时返回 `[1]`
- n = 2 时返回 `[1, 1]`
- n >= 3 时正常生成
`main` 中调用并打印前 20 项。
**期望输出(前 20 项):**
```
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
```
### 题目 4-3购物车模拟
编写一个模拟购物车的程序:
1. 定义一个 `Item` 结构体,包含 `name: String``price: f64`
2. 创建一个购物车 `Vec<Item>`,提供以下功能:
- `add_item(cart: &mut Vec<Item>, name: &str, price: f64)` — 添加商品
- `remove_item(cart: &mut Vec<Item>, name: &str)` — 按名称移除商品(只移除第一个匹配项)
- `total_price(cart: &Vec<Item>) -> f64` — 计算总价
- `list_items(cart: &Vec<Item>)` — 列出所有商品
3.`main` 中演示完整流程:添加 5 个商品,列出清单,计算总价,移除一个商品,再次列出清单和计算总价
**期望输出示例:**
```
购物车清单:
- 苹果: ¥3.50
- 香蕉: ¥2.00
- 牛奶: ¥6.80
- 面包: ¥5.00
- 鸡蛋: ¥12.00
当前总价: ¥29.30
移除"香蕉"后:
购物车清单:
- 苹果: ¥3.50
- 牛奶: ¥6.80
- 面包: ¥5.00
- 鸡蛋: ¥12.00
当前总价: ¥27.30
```
### 题目 4-4单词统计器
编写一个程序,统计一段文本中每个单词出现的次数。
1. 定义输入文本:`"the quick brown fox jumps over the lazy dog the fox was quick"`
2. 将文本按空格分割成单词,存入 `Vec<&str>`
3. 使用一个辅助的 Vec或使用 `HashMap`,但核心流程用 Vec 实现)统计每个不重复单词及其出现次数
4. 打印所有不重复单词及其出现次数(按单词字母顺序排序)
**提示:**
- 可以用 `Vec<(String, i32)>` 存储"单词-次数"对
- 遍历单词 Vec如果在统计 Vec 中找到该单词则计数+1否则追加新纪录
**期望输出:**
```
单词统计结果:
brown: 1
dog: 1
fox: 2
jumps: 1
lazy: 1
over: 1
quick: 2
the: 3
was: 1
```
### 题目 4-5矩阵转置
使用 `Vec<Vec<i32>>` 表示一个二维矩阵,编写一个函数 `transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>>` 实现矩阵转置。
- 输入:一个 m×n 的矩阵
- 输出:一个 n×m 的转置矩阵
- 使用迭代器方法实现,避免使用显式的 C 风格循环
`main` 中测试:
```
原始矩阵:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
转置后:
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
```
### 题目 4-6简易栈Stack实现
只使用 `Vec` 实现一个 `Stack<T>` 结构体,封装栈操作:
- `fn new() -> Self`
- `fn push(&mut self, item: T)`
- `fn pop(&mut self) -> Option<T>`
- `fn peek(&self) -> Option<&T>`
- `fn is_empty(&self) -> bool`
- `fn len(&self) -> usize`
`main` 中测试括号匹配功能:编写函数 `is_balanced(s: &str) -> bool`,利用你实现的 `Stack` 判断字符串中的 `()`、`[]`、`{}` 是否正确匹配。
测试用例:
```
测试用例:
"()" -> true
"()[]{}" -> true
"(]" -> false
"([)]" -> false
"{[]}" -> true
"(((" -> false
"" -> true
```
---
## 五、综合思考题
### 题目 5-1
阅读以下代码:
```rust
fn main() {
let mut v = vec![String::from("Rust"), String::from("is"), String::from("awesome")];
let first = &v[0]; // ①
v.push(String::from("!")); // ②
println!("{}", first); // ③
}
```
1. 这段代码能编译吗?为什么?
2. 如果改成 `let first = &v[0].clone();`,会怎样?为什么?
3. `v[0]``v.get(0)` 的区别在哪里?分别适用于什么场景?
4. 如果 `v` 的类型是 `Vec<i32>`,问题 1 的代码是否能编译?为什么?
### 题目 5-2
分析以下两种删除 Vec 中所有偶数元素的方式:
**方式 A**
```rust
fn remove_evens_a(v: &mut Vec<i32>) {
let mut i = 0;
while i < v.len() {
if v[i] % 2 == 0 {
v.remove(i);
} else {
i += 1;
}
}
}
```
**方式 B**
```rust
fn remove_evens_b(v: &mut Vec<i32>) {
v.retain(|x| x % 2 != 0);
}
```
1. 两种方式的时间复杂度分别是多少?为什么?
2. 方式 A 中的 `i += 1` 放在 `else` 分支中,如果去掉这个条件(每次循环都 `i += 1`)会发生什么?
3. `retain` 方法的底层原理是什么?它比手动 `remove` 高效在哪里?
### 题目 5-3
```rust
fn first_n_even(n: usize) -> Vec<i32> {
let mut result = Vec::new();
let mut num = 2;
while result.len() < n {
if num % 2 == 0 {
result.push(num);
}
num += 1;
}
result
}
```
1. 上述代码有没有更好的 Rust 风格写法?请使用迭代器重写。
2. 如果 `n` 非常大(比如 10000000从性能角度应该如何优化提示`Vec::with_capacity` 和迭代器)
### 题目 5-4
阅读以下代码,回答:
```rust
fn append_vector(v: &mut Vec<i32>, other: &mut Vec<i32>) {
v.append(other);
}
fn main() {
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![4, 5, 6];
append_vector(&mut v1, &mut v2);
println!("v1: {:?}", v1);
println!("v2: {:?}", v2);
}
```
1. `v2``append` 调用后会变成什么?为什么?
2. `append``extend` 有什么区别?各适用于什么场景?
3. 如果希望 `v2` 在追加后保持不变(即"复制追加"),应该使用什么方法?
### 题目 5-5
假设你正在实现一个游戏中的物品栏系统:
```rust
#[derive(Debug)]
struct Inventory {
items: Vec<Item>,
capacity: usize,
}
#[derive(Debug, Clone)]
struct Item {
name: String,
count: u32,
}
impl Inventory {
fn new(capacity: usize) -> Self {
Inventory {
items: Vec::new(),
capacity,
}
}
fn add_item(&mut self, item: Item) -> Result<(), String> {
// 待实现
todo!()
}
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
// 待实现
todo!()
}
}
```
1. 实现 `add_item`:如果物品已存在则叠加数量;如果背包已满则返回错误信息。注意借用检查器的约束。
2. 实现 `remove_item`:如果数量足够则减少,如果降至 0 则移除整个条目;如果物品不存在或数量不足则返回错误信息。
3. 这个设计中有一个潜在的问题:当背包容量已满但要添加的是已有物品时,叠加数量并不需要新增槽位。如何修改 `add_item` 的逻辑来正确处理这种情况?
---
## 参考答案
> 请独立完成再查看答案。
<details>
<summary>点击展开答案</summary>
### 一、概念辨析题
**1-1**:✅ 正确。`Vec<T>` 是泛型容器,所有元素必须是相同类型 `T`。如果要存储不同类型,需要使用枚举(如 `Vec<MyEnum>`)或 trait 对象(`Vec<Box<dyn MyTrait>>`)。
**1-2**:❌ 错误。空 Vec 调用 `pop()` 不会 panic而是返回 `None`。`pop` 的返回类型是 `Option<T>`,在空 Vec 上返回 `None`
**1-3**:❌ 错误。`Vec::new()` 创建一个容量为 0 的 Vec不分配堆内存`vec![]` 同样不分配。两者在这种情况下等价。但如果使用 `vec![1, 2, 3]` 和手动 `new``push` 三次,`vec![]` 可能预先分配合适的容量,性能略好。
**1-4**:❌ 错误。`v[3]` 索引越界时会 **panic**(程序崩溃),而 `v.get(3)` 返回 `Option<&T>`,越界时返回 `None`,不会崩溃。`get` 是安全的访问方式。
**1-5**:❌ 错误。`for item in v` 会消耗 Vec 的所有权,但 `for item in &v` 仅不可变借用,`for item in &mut v` 仅可变借用。只有转移所有权的遍历才会使 Vec 之后不可用。
**1-6**:✅ 正确。当 `len == capacity` 时再 pushVec 会自动分配一块更大的内存(通常是当前容量的两倍),将旧元素移动/拷贝到新内存,然后释放旧内存。
**1-7**:❌ 错误。`Vec::with_capacity(n)` 创建的 Vec **长度**为 0**容量**为 n。需要通过 `push` 等方法添加元素后长度才会增加。
**1-8**:❌ 错误。在遍历 Vec 时不能同时修改 Vec 本身(添加或删除元素),这违反了借用规则。遍历时持有的是对 Vec 或其元素的借用,而修改需要可变借用,两者不能共存。
### 二、填空题
**2-1**
```rust
let mut v = vec![1, 2, 3, 4, 5];
v.push(6);
let last = v.pop().unwrap();
println!("当前长度: {}", v.len());
match v.get(2) {
```
**2-2**
```rust
for i in &v {
print!("{} ", i);
}
// 可变遍历
for i in &mut v2 {
*i *= 2;
}
```
**2-3**
```rust
v.insert(2, 3);
v.remove(4);
v.remove(0);
```
**2-4**
```rust
enum Value {
Integer(i32),
Float(f64),
Text(String),
}
let row = vec![
Value::Integer(3),
Value::Float(3.14),
Value::Text(String::from("Hello")),
];
```
**2-5**
```rust
let slice1 = &v[1..4];
let slice2 = &v[..3];
let slice3 = &v[5..];
```
**2-6**
```rust
let mut v: Vec<i32> = Vec::with_capacity(100);
// ... 添加元素后
v.shrink_to_fit();
```
**2-7**
```rust
v.sort();
let has_five = v.contains(&5);
v.reverse();
```
**2-8**
```rust
v1.extend(&v2);
let drained: Vec<_> = v.drain(1..4).collect();
```
**2-9**
```rust
let result: Vec<i32> = v.iter()
.filter(|x| *x % 2 == 0)
.map(|x| x * x)
.collect();
let sum: i32 = v.iter().sum();
```
### 三、修复错误
**3-1**:不可变借用和可变借用冲突。`first` 是 `v` 的不可变引用,而 `push` 需要 `v` 的可变引用。修复:调整顺序,先使用 `first``push`,或在 push 前不保留引用。
```rust
let mut v = vec![1, 2, 3];
let first = v[0]; // 直接拷贝值i32 实现了 Copy
v.push(4);
println!("{}", first);
```
对于非 Copy 类型,可以:
```rust
let first = v[0].clone(); // 克隆值
v.push(4);
println!("{}", first);
```
**3-2**索引越界Vec 只有 5 个元素(索引 0~4访问 `v[10]` 会 panic。修复
```rust
let third = v.get(10); // 返回 None不会 panic
match third {
Some(val) => println!("{}", val),
None => println!("索引越界"),
}
```
**3-3**`Vec::new()` 无法推断类型,需要显式标注。且同一个 Vec 不能混装 `&str``i32`。修复:使用枚举:
```rust
enum Value<'a> {
Text(&'a str),
Number(i32),
}
let mut v: Vec<Value> = Vec::new();
v.push(Value::Text("hello"));
v.push(Value::Number(42));
```
**3-4**:在遍历 Vec 的同时修改 Vec 的大小,违反借用规则。修复:先收集要添加的元素,再 `extend`
```rust
let mut v = vec![1, 2, 3];
v.extend(vec![4, 4, 4]);
println!("{:?}", v);
```
**3-5**:一边按索引遍历一边删除元素会导致跳过元素(因为删除后后续元素前移)。修复:使用 `retain` 或从后往前遍历:
```rust
let mut v = vec![1, 2, 3, 4, 5];
v.retain(|x| x % 2 != 0);
println!("{:?}", v); // [1, 3, 5]
```
**3-6**`s` 借用了 `v` 中的元素,`drop(v)` 释放 `v``s` 变成悬垂引用。修复:先 `clone` 再释放:
```rust
let v = vec![String::from("hello")];
let s = v[0].clone(); // 获取独立的所有权
drop(v);
println!("{}", s);
```
**3-7**`map` 返回迭代器,需要 `.collect()` 消费才能得到 Vec。修复
```rust
let squared: Vec<i32> = v.iter().map(|x| x * x).collect();
```
**3-8**`pop` 返回 `Option<u32>`,不能直接用 `println!("{}", popped2)` 打印(类型不匹配,且值为 `None`)。修复:
```rust
println!("{:?}", popped); // Some(1)
println!("{:?}", popped2); // None
```
**3-9**`for item in v` 消耗了 `v` 的所有权,之后不能再用 `v`。修复:使用引用遍历:
```rust
for item in &v {
println!("{}", item);
}
println!("v 还有 {} 个元素", v.len());
```
**3-10**`slice` 是 `v` 的可变借用,`push` 需要另一个可变借用,冲突。修复:先操作切片,释放后再 push
```rust
let mut v = vec![1, 2, 3];
{
let slice = &mut v[0..2];
slice[0] = 10;
} // slice 的借用在此结束
v.push(4);
println!("{:?}", v); // [10, 2, 3, 4]
```
### 四、编程题
**4-1 成绩统计分析器:**
```rust
fn main() {
let scores = vec![85, 42, 96, 78, 55, 88, 91, 63, 100, 74];
println!("成绩列表: {:?}", scores);
let max = scores.iter().max().unwrap();
let min = scores.iter().min().unwrap();
let avg = scores.iter().sum::<i32>() as f64 / scores.len() as f64;
println!("最高分: {}", max);
println!("最低分: {}", min);
println!("平均分: {:.1}", avg);
let failing: Vec<i32> = scores.iter().filter(|&&s| s < 60).copied().collect();
println!("不及格成绩: {:?}", failing);
let has_perfect = scores.contains(&100);
println!("有满分: {}", if has_perfect { "是" } else { "否" });
let mut sorted = scores.clone();
sorted.sort_by(|a, b| b.cmp(a));
println!("排序后(高到低): {:?}", sorted);
}
```
**4-2 斐波那契数列生成器:**
```rust
fn fibonacci(n: usize) -> Vec<u64> {
match n {
0 => vec![],
1 => vec![1],
_ => {
let mut fib = vec![1, 1];
for _ in 2..n {
let next = fib[fib.len() - 1] + fib[fib.len() - 2];
fib.push(next);
}
fib
}
}
}
fn main() {
let fib = fibonacci(20);
println!("{:?}", fib);
}
```
**4-3 购物车模拟:**
```rust
struct Item {
name: String,
price: f64,
}
fn add_item(cart: &mut Vec<Item>, name: &str, price: f64) {
cart.push(Item {
name: name.to_string(),
price,
});
}
fn remove_item(cart: &mut Vec<Item>, name: &str) {
if let Some(pos) = cart.iter().position(|item| item.name == name) {
cart.remove(pos);
}
}
fn total_price(cart: &Vec<Item>) -> f64 {
cart.iter().map(|item| item.price).sum()
}
fn list_items(cart: &Vec<Item>) {
for item in cart {
println!(" - {}: ¥{:.2}", item.name, item.price);
}
}
fn main() {
let mut cart = Vec::new();
add_item(&mut cart, "苹果", 3.50);
add_item(&mut cart, "香蕉", 2.00);
add_item(&mut cart, "牛奶", 6.80);
add_item(&mut cart, "面包", 5.00);
add_item(&mut cart, "鸡蛋", 12.00);
println!("购物车清单:");
list_items(&cart);
println!("当前总价: ¥{:.2}", total_price(&cart));
remove_item(&mut cart, "香蕉");
println!("\n移除\"香蕉\"后:");
println!("购物车清单:");
list_items(&cart);
println!("当前总价: ¥{:.2}", total_price(&cart));
}
```
**4-4 单词统计器:**
```rust
fn main() {
let text = "the quick brown fox jumps over the lazy dog the fox was quick";
let words: Vec<&str> = text.split_whitespace().collect();
let mut stats: Vec<(String, i32)> = Vec::new();
for word in &words {
if let Some(pos) = stats.iter().position(|(w, _)| w == word) {
stats[pos].1 += 1;
} else {
stats.push((word.to_string(), 1));
}
}
stats.sort_by(|a, b| a.0.cmp(&b.0));
println!("单词统计结果:");
for (word, count) in &stats {
println!(" {}: {}", word, count);
}
}
```
**4-5 矩阵转置:**
```rust
fn transpose(matrix: &Vec<Vec<i32>>) -> Vec<Vec<i32>> {
if matrix.is_empty() {
return vec![];
}
let rows = matrix.len();
let cols = matrix[0].len();
(0..cols)
.map(|col| {
(0..rows)
.map(|row| matrix[row][col])
.collect()
})
.collect()
}
fn main() {
let matrix = vec![
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9],
];
println!("原始矩阵:");
for row in &matrix {
println!("{:?}", row);
}
let transposed = transpose(&matrix);
println!("\n转置后:");
for row in &transposed {
println!("{:?}", row);
}
}
```
**4-6 简易栈实现:**
```rust
struct Stack<T> {
data: Vec<T>,
}
impl<T> Stack<T> {
fn new() -> Self {
Stack { data: Vec::new() }
}
fn push(&mut self, item: T) {
self.data.push(item);
}
fn pop(&mut self) -> Option<T> {
self.data.pop()
}
fn peek(&self) -> Option<&T> {
self.data.last()
}
fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn len(&self) -> usize {
self.data.len()
}
}
fn is_balanced(s: &str) -> bool {
let mut stack = Stack::new();
for ch in s.chars() {
match ch {
'(' | '[' | '{' => stack.push(ch),
')' => {
if stack.pop() != Some('(') {
return false;
}
}
']' => {
if stack.pop() != Some('[') {
return false;
}
}
'}' => {
if stack.pop() != Some('{') {
return false;
}
}
_ => {}
}
}
stack.is_empty()
}
fn main() {
let tests = [
("()", true),
("()[]{}", true),
("(]", false),
("([)]", false),
("{[]}", true),
("(((", false),
("", true),
];
println!("括号匹配测试:");
for (input, expected) in tests {
let result = is_balanced(input);
let status = if result == expected { "✓" } else { "✗" };
println!(" {} \"{}\" -> {} (期望: {})", status, input, result, expected);
}
}
```
### 五、综合思考题
**5-1**
1. 不能编译。`first` 是 `v[0]` 的不可变引用,而 `v.push()` 需要可变引用。即便 `push` 触发的内存重分配不影响索引 0 的元素(逻辑上 String 的数据在堆上),但 Rust 的借用规则在编译期不能精确判断这一点,因此直接拒绝在持有不可变引用时进行可变操作。
2. `v[0].clone()` 会创建一个新的 `String``first` 拥有这个新 String 的所有权,不再依赖 `v`。因此 `v.push()` 不受影响,可以编译。
3. `v[0]` 返回 `&T`,越界时 panic`v.get(0)` 返回 `Option<&T>`,越界时返回 `None`。`v[0]` 适用于确定索引有效的场景,`v.get(0)` 适用于索引可能越界的场景。
4. 可以编译。因为 `i32` 实现了 `Copy` trait`let first = &v[0]` 在后续使用中,由于 `first``&i32``println!` 打印时需要解引用为 `i32` 值。但关键是将 `let first = &v[0]` 改成 `let first = v[0]` 后,`first` 是 `i32`Copy`v` 无关,更安全。使用 `&v[0]` 仍然会有借用冲突。
**5-2**
1. 方式 A 的时间复杂度是 O(n²),因为每次 `remove(i)` 都需要将后续元素向前移动一位。方式 B 的 `retain` 是 O(n),内部使用一次遍历 + 一次批量移动实现。
2. 如果每次都 `i += 1`,删除偶数元素后索引不会重新检查当前位置(因为后续元素前移了),会**跳过**紧随其后的元素。例如 `[2, 2, 1]`,删除索引 0 的元素 2 后,数组变为 `[2, 1]`i 变成 1第二个 2 被跳过。
3. `retain` 的底层原理类似于"双指针":慢指针 `j` 指向下一个要保留的位置,快指针 `i` 遍历所有元素。当元素满足保留条件时,移动到 `j` 位置并使 `j++`;否则跳过。遍历完成后截断到 `j`。避免了每次删除都移动后续元素的 O(n²) 开销。
**5-3**
1. 迭代器重写:
```rust
fn first_n_even(n: usize) -> Vec<i32> {
(1..).filter(|x| x % 2 == 0).take(n).collect()
}
```
2. 优化:
```rust
fn first_n_even_optimized(n: usize) -> Vec<i32> {
let mut result = Vec::with_capacity(n); // 预分配
(1..).filter(|x| x % 2 == 0).take(n).for_each(|x| result.push(x));
result
}
// 或者更简单的:从 2 开始每次加 2
fn first_n_even_optimized(n: usize) -> Vec<i32> {
(2..).step_by(2).take(n).collect()
}
```
使用 `Vec::with_capacity(n)` 预分配可以避免多次重分配的开销。
**5-4**
1. `v2` 变成空 Vec `[]`。因为 `append` 方法将所有元素**移动**到目标 Vec并清空源 Vec。这是 O(1) 时间,只交换了指针。
2. 区别:
- `append`:移动所有元素,源 Vec 被清空。适用于需要转移所有权的场景。
- `extend`:从迭代器克隆/复制元素到目标 Vec源数据保持不变。适用于需要保留源的场景。
- 性能上 `append` 只需交换内部指针,是 O(1)`extend` 需要逐个复制,是 O(n)。
3. 使用 `extend``clone`
```rust
v1.extend_from_slice(&v2); // v2 保持不变
// 或
v1.extend(v2.iter().cloned()); // 迭代器方式
```
**5-5**
```rust
fn add_item(&mut self, item: Item) -> Result<(), String> {
// 先检查是否有同名物品
if let Some(existing) = self.items.iter_mut().find(|i| i.name == item.name) {
existing.count += item.count;
return Ok(());
}
// 新增物品,检查容量
if self.items.len() >= self.capacity {
return Err(format!("背包已满({}/{}", self.items.len(), self.capacity));
}
self.items.push(item);
Ok(())
}
fn remove_item(&mut self, name: &str, count: u32) -> Result<(), String> {
let pos = self
.items
.iter()
.position(|i| i.name == name)
.ok_or(format!("物品「{}」不存在", name))?;
if self.items[pos].count < count {
return Err(format!(
"物品「{}」数量不足(需要 {},当前 {}",
name, count, self.items[pos].count
));
}
self.items[pos].count -= count;
if self.items[pos].count == 0 {
self.items.remove(pos);
}
Ok(())
}
```
关键点是先检查是否可以叠加已有物品(不需要新槽位),再检查容量限制。这样当物品已存在时,即使背包"已满"也能正确叠加数量。
</details>