Rust 练习册:钻石与字符串模式生成
在编程练习中,我们经常会遇到需要生成特定图案的问题。钻石(Diamond)问题要求我们根据给定的字母生成一个对称的钻石形状图案。这个问题虽然看似简单,但它涉及到了字符串操作、模式识别和几何思维等多个方面。在 Exercism 的 “diamond” 练习中,我们将实现这个有趣的图案生成算法,这不仅能帮助我们掌握字符串处理技巧,还能深入学习 Rust 中的迭代器使用和模式生成技术。
什么是钻石图案?
钻石图案是一种对称的字符图案,具有以下特点:
- 对称性:图案关于中心行对称
- 字母序列:从 ‘A’ 开始,逐行递增到指定字母,然后递减回 ‘A’
- 间距规则:每行的字符之间有特定的空格模式
- 中心对齐:整个图案在中心对齐
例如,对于字母 ‘C’,钻石图案如下:
A
B B
C C
B B
A
让我们先看看练习提供的函数签名:
pub fn get_diamond(c: char) -> Vec<String> {
unimplemented!(
"Return the vector of strings which represent the diamond with particular char {}",
c
);
}
我们需要实现这个函数,它应该:
- 接收一个大写字母作为参数
- 生成对应的钻石图案
- 返回一个字符串向量,每个字符串代表图案的一行
算法分析
1. 理解图案规律
通过观察测试用例,我们可以发现钻石图案的规律:
- 行数计算:对于字母 ‘C’(在字母表中是第3个字母),总共有 2×3-1 = 5 行
- 每行结构:
- 第一行:(n-1)个空格 + ‘A’ + (n-1)个空格
- 中间行:前导空格 + 字母 + 中间空格 + 字母 + 后导空格
- 中心行:无前导空格 + 字母 + 中间空格 + 字母 + 无后导空格
- 对称性:下半部分是上半部分的镜像(不包括中心行)
2. 基础实现
pub fn get_diamond(c: char) -> Vec<String> {
// 检查输入是否为大写字母
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1; // 计算字母在字母表中的位置(A=1, B=2, ...)
let width = (n * 2 - 1) as usize; // 计算每行的总宽度
let mut result = Vec::with_capacity((n * 2 - 1) as usize);
// 生成上半部分(包括中心行)
for i in 0..n {
let current_char = (b'A' + i) as char;
let outer_spaces = (n - i - 1) as usize;
if i == 0 {
// 第一行,只有字符'A'
let line = format!("{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(outer_spaces)
);
result.push(line);
} else {
// 其他行,有两个字符
let inner_spaces = (i * 2 - 1) as usize;
let line = format!("{}{}{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(inner_spaces),
current_char,
" ".repeat(outer_spaces)
);
result.push(line);
}
}
// 生成下半部分(不包括中心行)
for i in (0..n-1).rev() {
let current_char = (b'A' + i) as char;
let outer_spaces = (n - i - 1) as usize;
if i == 0 {
// 最后一行,只有字符'A'
let line = format!("{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(outer_spaces)
);
result.push(line);
} else {
// 其他行,有两个字符
let inner_spaces = (i * 2 - 1) as usize;
let line = format!("{}{}{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(inner_spaces),
current_char,
" ".repeat(outer_spaces)
);
result.push(line);
}
}
result
}
3. 优化实现
pub fn get_diamond(c: char) -> Vec<String> {
// 检查输入是否为大写字母
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let width = (n * 2 - 1) as usize;
let mut result = Vec::with_capacity((n * 2 - 1) as usize);
// 生成所有行
for i in 0..(n * 2 - 1) {
let row_index = if i < n { i } else { 2 * n - 2 - i };
let current_char = (b'A' + row_index) as char;
let outer_spaces = (n - row_index - 1) as usize;
let line = if row_index == 0 {
// 只有一个字符的行
format!("{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(outer_spaces)
)
} else {
// 有两个字符的行
let inner_spaces = (row_index * 2 - 1) as usize;
format!("{}{}{}{}{}",
" ".repeat(outer_spaces),
current_char,
" ".repeat(inner_spaces),
current_char,
" ".repeat(outer_spaces)
)
};
result.push(line);
}
result
}
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn test_a() {
assert_eq!(get_diamond('A'), vec!["A"]);
}
最简单的情况,只有一行。
#[test]
fn test_b() {
#[rustfmt::skip]
assert_eq!(
get_diamond('B'),
vec![
" A ",
"B B",
" A ",
]
);
}
3行的钻石,中心行有两个字符,其他行各有一个字符。
#[test]
fn test_c() {
#[rustfmt::skip]
assert_eq!(
get_diamond('C'),
vec![
" A ",
" B B ",
"C C",
" B B ",
" A ",
]
);
}
5行的钻石,每行都有正确的空格数。
#[test]
fn test_e() {
assert_eq!(
get_diamond('Z'),
vec![
" A ",
" B B ",
// ... 中间省略 ...
" A ",
]
);
}
对于 ‘Z’,生成一个51行的大型钻石图案。
完整实现
考虑所有边界情况的完整实现:
pub fn get_diamond(c: char) -> Vec<String> {
// 验证输入
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1; // 字母的位置(A=1, B=2, ...)
let rows = (n * 2 - 1) as usize; // 总行数
let width = rows; // 每行的宽度
let mut diamond = Vec::with_capacity(rows);
// 生成每一行
for i in 0..rows {
// 计算当前行对应的字母索引
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
// 构造当前行
let line = if letter_index == 0 {
// 'A'行,只有一个字符
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
// 其他行,有两个字符
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
};
diamond.push(line);
}
diamond
}
性能优化版本
考虑性能的优化实现:
pub fn get_diamond(c: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
let mut diamond = Vec::with_capacity(rows);
// 预分配字符串以避免重复分配
for i in 0..rows {
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
// 使用 String::with_capacity 预分配容量
let mut line = String::with_capacity((2 * n - 1) as usize);
// 添加前导空格
for _ in 0..outer_spaces {
line.push(' ');
}
// 添加第一个字符
line.push(current_letter);
// 添加中间空格和第二个字符(如果不是'A'行)
if letter_index > 0 {
let inner_spaces = (letter_index * 2 - 1) as usize;
for _ in 0..inner_spaces {
line.push(' ');
}
line.push(current_letter);
}
// 添加后导空格
for _ in 0..outer_spaces {
line.push(' ');
}
diamond.push(line);
}
diamond
}
函数式风格实现
使用函数式编程风格的实现:
pub fn get_diamond(c: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
(0..rows)
.map(|i| {
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
if letter_index == 0 {
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
}
})
.collect()
}
错误处理和边界情况
考虑更多边界情况的实现:
#[derive(Debug, PartialEq)]
pub enum DiamondError {
InvalidInput,
}
pub fn get_diamond(c: char) -> Result<Vec<String>, DiamondError> {
// 验证输入
if !c.is_ascii_uppercase() {
return Err(DiamondError::InvalidInput);
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
let mut diamond = Vec::with_capacity(rows);
for i in 0..rows {
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
let line = if letter_index == 0 {
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
};
diamond.push(line);
}
Ok(diamond)
}
// 保持原始接口以兼容测试
pub fn get_diamond_compat(c: char) -> Vec<String> {
get_diamond(c).unwrap_or_else(|_| vec![])
}
扩展功能
基于基础实现,我们可以添加更多功能:
pub struct DiamondGenerator;
impl DiamondGenerator {
pub fn new() -> Self {
DiamondGenerator
}
pub fn generate(&self, c: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
let mut diamond = Vec::with_capacity(rows);
for i in 0..rows {
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
let line = if letter_index == 0 {
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
};
diamond.push(line);
}
diamond
}
// 生成填充字符版本的钻石
pub fn generate_with_fill(&self, c: char, fill_char: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
let mut diamond = Vec::with_capacity(rows);
for i in 0..rows {
let letter_index = if i < n as usize {
i
} else {
rows - 1 - i
} as u8;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
let line = if letter_index == 0 {
format!(
"{}{}{}",
fill_char.to_string().repeat(outer_spaces),
current_letter,
fill_char.to_string().repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
fill_char.to_string().repeat(outer_spaces),
current_letter,
fill_char.to_string().repeat(inner_spaces),
current_letter,
fill_char.to_string().repeat(outer_spaces)
)
};
diamond.push(line);
}
diamond
}
// 获取钻石的尺寸信息
pub fn get_dimensions(&self, c: char) -> Option<(usize, usize)> {
if !c.is_ascii_uppercase() {
return None;
}
let n = c as u8 - b'A' + 1;
let rows = (2 * n - 1) as usize;
let width = rows;
Some((rows, width))
}
}
实际应用场景
钻石图案生成在实际开发中有以下应用:
- 艺术字生成:在终端或文本界面中生成装饰性文字
- 教育工具:作为编程教学中的循环和字符串处理示例
- 游戏开发:ASCII艺术和图案生成
- 用户界面:终端应用中的视觉元素
- 代码艺术:生成有趣的ASCII艺术图案
- 测试图案:用于测试显示和布局功能
算法复杂度分析
-
时间复杂度:O(n²)
- 需要生成 2n-1 行
- 每行最多需要处理 2n-1 个字符
- 总体复杂度为 O(n²)
-
空间复杂度:O(n²)
- 需要存储 2n-1 个字符串
- 每个字符串长度为 2n-1
- 总体空间复杂度为 O(n²)
与其他实现方式的比较
// 使用递归实现
pub fn get_diamond_recursive(c: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
fn build_diamond(n: u8, current: u8) -> Vec<String> {
if current > n {
return vec![];
}
let letter_index = current - 1;
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - current) as usize;
let line = if letter_index == 0 {
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
};
let mut result = vec![line];
if current < n {
let mut rest = build_diamond(n, current + 1);
result.append(&mut rest);
}
result
}
let n = c as u8 - b'A' + 1;
let mut top_half = build_diamond(n, 1);
// 添加下半部分(镜像,不包括中心行)
let mut bottom_half: Vec<String> = top_half[..top_half.len()-1]
.iter()
.rev()
.cloned()
.collect();
top_half.append(&mut bottom_half);
top_half
}
// 使用迭代器链实现
pub fn get_diamond_iterator(c: char) -> Vec<String> {
if !c.is_ascii_uppercase() {
return vec![];
}
let n = c as u8 - b'A' + 1;
let indices: Vec<u8> = (0..n).chain((0..n-1).rev()).collect();
indices
.iter()
.map(|&letter_index| {
let current_letter = (b'A' + letter_index) as char;
let outer_spaces = (n - letter_index - 1) as usize;
if letter_index == 0 {
format!(
"{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(outer_spaces)
)
} else {
let inner_spaces = (letter_index * 2 - 1) as usize;
format!(
"{}{}{}{}{}",
" ".repeat(outer_spaces),
current_letter,
" ".repeat(inner_spaces),
current_letter,
" ".repeat(outer_spaces)
)
}
})
.collect()
}
总结
通过 diamond 练习,我们学到了:
- 字符串处理:掌握了复杂的字符串格式化和拼接技巧
- 模式识别:学会了识别和实现几何图案的生成规律
- 迭代器使用:熟练使用迭代器链来简化代码
- 性能优化:了解了预分配内存等优化技巧
- 边界处理:学会了处理各种边界情况
- 错误处理:理解了如何处理无效输入
这些技能在实际开发中非常有用,特别是在处理文本界面、生成艺术字、实现用户界面元素和进行字符串操作时。钻石图案生成虽然看起来简单,但它涉及到了字符串处理、模式识别和算法实现等许多核心概念,是学习 Rust 字符串处理的良好起点。
通过这个练习,我们也看到了 Rust 在字符串处理和迭代器使用方面的强大能力,以及如何用简洁且高效的方式实现复杂的文本模式。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)