在编程练习中,我们经常会遇到需要生成特定图案的问题。钻石(Diamond)问题要求我们根据给定的字母生成一个对称的钻石形状图案。这个问题虽然看似简单,但它涉及到了字符串操作、模式识别和几何思维等多个方面。在 Exercism 的 “diamond” 练习中,我们将实现这个有趣的图案生成算法,这不仅能帮助我们掌握字符串处理技巧,还能深入学习 Rust 中的迭代器使用和模式生成技术。

什么是钻石图案?

钻石图案是一种对称的字符图案,具有以下特点:

  1. 对称性:图案关于中心行对称
  2. 字母序列:从 ‘A’ 开始,逐行递增到指定字母,然后递减回 ‘A’
  3. 间距规则:每行的字符之间有特定的空格模式
  4. 中心对齐:整个图案在中心对齐

例如,对于字母 ‘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. 接收一个大写字母作为参数
  2. 生成对应的钻石图案
  3. 返回一个字符串向量,每个字符串代表图案的一行

算法分析

1. 理解图案规律

通过观察测试用例,我们可以发现钻石图案的规律:

  1. 行数计算:对于字母 ‘C’(在字母表中是第3个字母),总共有 2×3-1 = 5 行
  2. 每行结构
    • 第一行:(n-1)个空格 + ‘A’ + (n-1)个空格
    • 中间行:前导空格 + 字母 + 中间空格 + 字母 + 后导空格
    • 中心行:无前导空格 + 字母 + 中间空格 + 字母 + 无后导空格
  3. 对称性:下半部分是上半部分的镜像(不包括中心行)

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))
    }
}

实际应用场景

钻石图案生成在实际开发中有以下应用:

  1. 艺术字生成:在终端或文本界面中生成装饰性文字
  2. 教育工具:作为编程教学中的循环和字符串处理示例
  3. 游戏开发:ASCII艺术和图案生成
  4. 用户界面:终端应用中的视觉元素
  5. 代码艺术:生成有趣的ASCII艺术图案
  6. 测试图案:用于测试显示和布局功能

算法复杂度分析

  1. 时间复杂度:O(n²)

    • 需要生成 2n-1 行
    • 每行最多需要处理 2n-1 个字符
    • 总体复杂度为 O(n²)
  2. 空间复杂度: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 练习,我们学到了:

  1. 字符串处理:掌握了复杂的字符串格式化和拼接技巧
  2. 模式识别:学会了识别和实现几何图案的生成规律
  3. 迭代器使用:熟练使用迭代器链来简化代码
  4. 性能优化:了解了预分配内存等优化技巧
  5. 边界处理:学会了处理各种边界情况
  6. 错误处理:理解了如何处理无效输入

这些技能在实际开发中非常有用,特别是在处理文本界面、生成艺术字、实现用户界面元素和进行字符串操作时。钻石图案生成虽然看起来简单,但它涉及到了字符串处理、模式识别和算法实现等许多核心概念,是学习 Rust 字符串处理的良好起点。

通过这个练习,我们也看到了 Rust 在字符串处理和迭代器使用方面的强大能力,以及如何用简洁且高效的方式实现复杂的文本模式。这种结合了安全性和性能的语言特性正是 Rust 的魅力所在。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐