如何在Rust中无需手动罗列所有值,获取两个枚举的笛卡尔积?
在Rust中无需手动罗列值生成完整扑克牌组的方法
问题背景
我们用两个枚举分别表示扑克牌的花色和点数:
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] enum Suit { Spades, Hearts, Diamonds, Clubs, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] enum Rank { Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace, }
要生成完整的52张牌,本质是求这两个枚举的笛卡尔积,但枚举默认没实现Iterator trait。不想手动罗列所有枚举值,也不想用过程宏,有没有简便方法?另外,这种建模方式是否合理?
解决方案
方法1:借助num_enum crate(轻量属性宏,无需手写大量转换代码)
num_enum可以帮我们给枚举自动绑定数值,并实现FromPrimitive trait,这样就能通过数值范围遍历所有枚举值。
首先在Cargo.toml添加依赖:
[dependencies] num_enum = "0.7"
修改枚举并实现迭代器:
use num_enum::{IntoPrimitive, FromPrimitive}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, IntoPrimitive, FromPrimitive)] #[repr(u8)] enum Suit { Spades = 0, Hearts = 1, Diamonds = 2, Clubs = 3, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, IntoPrimitive, FromPrimitive)] #[repr(u8)] enum Rank { Two = 0, Three = 1, Four = 2, Five = 3, Six = 4, Seven = 5, Eight = 6, Nine = 7, Ten = 8, Jack = 9, Queen = 10, King = 11, Ace = 12, } // 实现Suit的迭代器 struct SuitIter(u8); impl Iterator for SuitIter { type Item = Suit; fn next(&mut self) -> Option<Self::Item> { let res = Suit::from_primitive(self.0); self.0 += 1; res } } impl Suit { fn iter() -> SuitIter { SuitIter(0) } } // 实现Rank的迭代器 struct RankIter(u8); impl Iterator for RankIter { type Item = Rank; fn next(&mut self) -> Option<Self::Item> { let res = Rank::from_primitive(self.0); self.0 += 1; res } } impl Rank { fn iter() -> RankIter { RankIter(0) } }
生成完整牌组:
#[derive(Debug)] struct Card { suit: Suit, rank: Rank, } fn main() { let deck: Vec<Card> = Suit::iter() .flat_map(|suit| Rank::iter().map(move |rank| Card { suit, rank })) .collect(); println!("{:?}", deck); }
方法2:纯手动实现(无需第三方库)
如果不想引入外部依赖,可以手动给枚举分配数值并实现TryFrom,通过数值范围遍历:
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] enum Suit { Spades = 0, Hearts = 1, Diamonds = 2, Clubs = 3, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] enum Rank { Two = 0, Three = 1, Four = 2, Five = 3, Six = 4, Seven = 5, Eight = 6, Nine = 7, Ten = 8, Jack = 9, Queen = 10, King = 11, Ace = 12, } // 实现Suit的TryFrom转换 impl TryFrom<u8> for Suit { type Error = (); fn try_from(value: u8) -> Result<Self, Self::Error> { match value { 0 => Ok(Suit::Spades), 1 => Ok(Suit::Hearts), 2 => Ok(Suit::Diamonds), 3 => Ok(Suit::Clubs), _ => Err(()), } } } // 实现Rank的TryFrom转换 impl TryFrom<u8> for Rank { type Error = (); fn try_from(value: u8) -> Result<Self, Self::Error> { match value { 0 => Ok(Rank::Two), 1 => Ok(Rank::Three), 2 => Ok(Rank::Four), 3 => Ok(Rank::Five), 4 => Ok(Rank::Six), 5 => Ok(Rank::Seven), 6 => Ok(Rank::Eight), 7 => Ok(Rank::Nine), 8 => Ok(Rank::Ten), 9 => Ok(Rank::Jack), 10 => Ok(Rank::Queen), 11 => Ok(Rank::King), 12 => Ok(Rank::Ace), _ => Err(()), } } }
生成牌组:
#[derive(Debug)] struct Card { suit: Suit, rank: Rank, } fn main() { let deck: Vec<Card> = (0..4) .filter_map(|val| Suit::try_from(val).ok()) .flat_map(|suit| { (0..13) .filter_map(|val| Rank::try_from(val).ok()) .map(move |rank| Card { suit, rank }) }) .collect(); println!("{:?}", deck); }
建模方式合理性说明
用枚举建模花色和点数完全是正确的选择,这是Rust中处理有限固定值集合的惯用方式。枚举能提供严格的类型安全,避免出现无效的花色或点数(比如用整数可能出现5这种非法花色),比直接用整数表示更可靠、可读性更强。
内容的提问来源于stack exchange,提问作者Jared Smith
相关产品推荐
相关产品推荐

