[炼手Rust]你对什么过敏

198 阅读2分钟

Hi,大家好,这里是炼手Rust专栏,我是xiaoK,今天来看个rust语言设计相关的挑战:你对什么过敏。

2546496.jpg

提问

阿峰对很多东西过敏,他去医院诊断,拿到一个过敏评分,我们需要确定阿峰是否对特定物品过敏,以及阿峰的过敏物品完整列表。

过敏测试会产生一个单一的数字分数,其中包含阿峰的所有过敏的信息。

已测试的项目列表为:

鸡蛋 (1)
花生 (2)
贝类 (4)
草莓 (8)
西红柿 (16)
巧克力 (32)
花粉 (64)
猫 (128)

因此,如果阿峰对花生和巧克力过敏,他的得分为 34。

现在,给出 34 分,我们的程序应该能够说:

阿峰是否对上面列出的任何一种过敏原过敏。
阿峰的所有过敏原。

注意:给定分数可能包括上面未列出的过敏原(即分数为 256、512、1024 等的过敏原)。我们的程序应该忽略这些。例如,如果过敏评分为 257,则我们程序应仅报告鸡蛋 (1) 过敏。

模板:

pub struct Allergies;

#[derive(Debug, PartialEq, Eq)]
pub enum Allergen {
    Eggs,
    Peanuts,
    Shellfish,
    Strawberries,
    Tomatoes,
    Chocolate,
    Pollen,
    Cats,
}

impl Allergies {
    pub fn new(score: u32) -> Self {
        todo!("Given the '{score}' score, construct a new Allergies struct.");
    }

    pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
        todo!("Determine if the patient is allergic to the '{allergen:?}' allergen.");
    }

    pub fn allergies(&self) -> Vec<Allergen> {
        todo!("Return the list of allergens contained within the score with which the Allergies struct was made.");
    }
}

分析

这是一个非常典型的程序设计挑战,并不着重于算法的考察,而是考察程序设计的逻辑。

按照题设,我们不难发现,算法上该挑战其实是考察位运算的应用。Eggs为 1,Peanuts为 2,即1 << n - 1,判断是否对过敏原过敏,可以使用与运算a & b即可。

解决方案

pub struct Allergies(u32);

#[derive(Debug, PartialEq, Eq)]
pub enum Allergen {
    Eggs,
    Peanuts,
    Shellfish,
    Strawberries,
    Tomatoes,
    Chocolate,
    Pollen,
    Cats,
}

impl Allergen {
    pub fn ordinal(&self) -> u32 {
        match self {
            Allergen::Eggs => 0,
            Allergen::Peanuts => 1,
            Allergen::Shellfish => 2,
            Allergen::Strawberries => 3,
            Allergen::Tomatoes => 4,
            Allergen::Chocolate => 5,
            Allergen::Pollen => 6,
            Allergen::Cats => 7,
        }
    }

    pub fn values() -> Vec<Allergen> {
        vec![
            Allergen::Eggs,
            Allergen::Peanuts,
            Allergen::Shellfish,
            Allergen::Strawberries,
            Allergen::Tomatoes,
            Allergen::Chocolate,
            Allergen::Pollen,
            Allergen::Cats,
        ]
    }
}

impl Allergies {
    pub fn new(score: u32) -> Self {
        Self(score)
    }

    pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
        ((1 << allergen.ordinal()) & self.0) != 0
    }

    pub fn allergies(&self) -> Vec<Allergen> {
        let mut all = Allergen::values();
        let all = all.into_iter().filter(|s| self.is_allergic_to(s)).collect();
        all
    }
}

Trick

  1. 此处我们创建了一个方法ordinal返回相关的枚举值,当然在rust中也可以直接定义枚举的值,这个读者可以自行选择。
  2. 过滤使用filter,传入一个lambda表达式作为判断依据。
  3. 如果想简单的列出枚举的所有值和序数ordinal,可以借助第三方库enum_ordinalize,读者有空可以自行使用。