第19题-数周日

本文包含了欧拉项目第19题的解法

问题描述

这个问题翻译为中文就是:

给定以下信息

  1. 1900年1月1日是周一

  2. 30天一个月的月份有4月、6月、9月、11月,除了2月外剩下的月份都是31天,单独说2月,如果是平年则28天,如果是闰年则29天

  3. 闰年是能被4整除但是不能被100整除,或者是能被400整除的年份

求解:

在1901年1月1日到2000年12月31日之间有几个当月第一天是周日的日子

解题思路

本题没有什么很难的地方,主要是实现一个简单的日历,然后从日历中查出符合条件的日期来就行。正经的日历都是根据各种天文知识来实现的,不过这个简单的日历可以根据题目中给定的第一条信息推算出来,从1900年1月1日起,可以往前往后推算出对应的日期

rust语言实现

p19.rs
use algorithms::misc::leap_year::is_leap_year;
use std::cmp::Ordering;

pub struct SimpleDate {
    pub year: usize,
    pub month: usize,
    pub day: usize,
}

impl SimpleDate {
    pub fn new(year: usize, month: usize, day: usize) -> Self {
        SimpleDate { year, month, day }
    }

    pub fn to_string(&self) -> String {
        return format!("{}-{}-{}", self.year, self.month, self.day);
    }

    /// get the day in week, Sunday -> 0, Monday -> 1 ... Sat -> 6
    pub fn weekday(&self) -> usize {
        let mut start_date = SimpleDate::new(1900, 1, 1);
        let mut day_diff = 1; // this day is monday
        loop {
            if start_date >= *self {
                break;
            }
            day_diff += 1;
            start_date = start_date.next_day();
        }
        day_diff % 7 // sunday is 0
    }

    /// add a day to self
    pub fn next_day(&mut self) -> SimpleDate {
        if self.month == 12 {
            if self.day == 31 {
                // last day of the year, to a new year
                SimpleDate::new(self.year + 1, 1, 1)
            } else {
                // just add one day
                SimpleDate::new(self.year, self.month, self.day + 1)
            }
        } else if [4, 6, 9, 11].contains(&self.month) {
            // 30-day months
            if self.day == 30 {
                SimpleDate::new(self.year, self.month + 1, 1)
            } else {
                SimpleDate::new(self.year, self.month, self.day + 1)
            }
        } else if [1, 3, 5, 7, 8, 10].contains(&self.month) {
            if self.day == 31 {
                SimpleDate::new(self.year, self.month + 1, 1)
            } else {
                SimpleDate::new(self.year, self.month, self.day + 1)
            }
        } else if self.month == 2 {
            if is_leap_year(self.year) {
                if self.day == 29 {
                    SimpleDate::new(self.year, self.month + 1, 1)
                } else {
                    SimpleDate::new(self.year, self.month, self.day + 1)
                }
            } else if self.day == 28 {
                SimpleDate::new(self.year, self.month + 1, 1)
            } else {
                SimpleDate::new(self.year, self.month, self.day + 1)
            }
        } else {
            panic!("error here, invalid month");
        }
    }
}

impl PartialEq for SimpleDate {
    fn eq(&self, other: &SimpleDate) -> bool {
        self.year == other.year && self.month == other.month && self.day == other.day
    }
}

impl PartialOrd for SimpleDate {
    fn partial_cmp(&self, other: &SimpleDate) -> Option<Ordering> {
        if self.year < other.year {
            return Some(Ordering::Less);
        } else if self.year == other.year {
            if self.month < other.month {
                return Some(Ordering::Less);
            } else if self.month == other.month {
                if self.day < other.day {
                    return Some(Ordering::Less);
                } else if self.day == other.day {
                    return Some(Ordering::Equal);
                }
                return Some(Ordering::Greater);
            }
            return Some(Ordering::Greater);
        }
        Some(Ordering::Greater)
    }
}

pub fn count_sundays(s: &SimpleDate, end_date: &SimpleDate) -> usize {
    let mut start_date = SimpleDate::new(s.year, s.month, s.day);
    let mut weekday = start_date.weekday();
    let mut num = 0;
    while start_date < *end_date {
        if weekday == 0 && start_date.day == 1 {
            num += 1;
        }
        start_date = start_date.next_day();
        weekday += 1;
        weekday %= 7;
    }
    num
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_next_day() {
        let mut start_date = SimpleDate::new(1900, 1, 1);
        start_date = start_date.next_day();
        assert_eq!(true, SimpleDate::new(1900, 1, 2) == start_date);
        assert_eq!(true, start_date < SimpleDate::new(1900, 1, 3));
    }

    #[test]
    fn test_weekday() {
        let mut start_date = SimpleDate::new(1900, 1, 1);
        start_date = start_date.next_day();
        start_date = start_date.next_day();

        assert_eq!(3, start_date.weekday());
    }

    #[test]
    fn test_count_days() {
        assert_eq!(
            2,
            count_sundays(&SimpleDate::new(1900, 1, 1), &SimpleDate::new(1900, 12, 31))
        ); // 1900-4-1, 1900-7-1
        assert_eq!(
            171,
            count_sundays(&SimpleDate::new(1901, 1, 1), &SimpleDate::new(2000, 12, 31))
        );
    }
}

代码解释

  1. 构造一个简单的日期结构体 SimpleDate,并且赋予一些简单的方法,next_day是用来得到下一天日期的,weekday用来返回当前日期对应星期几

  2. 通过实现PartialEq和PartialOrder来比较日期大小

  3. 循环遍历获取到目标日期

下面测试模块中包含了几个测试用例,分别测试对应的几个公共方法,通过保证每个方法是正确的,从而让整体程序保证正确这种方法叫做单元测试。最后面那个test_count_days方法要用已知的结果来验证程序正确性,叫做功能测试。

通过生成1900年的日历,我们发现1号是周日的日期有:1900-04-01, 1900-07-01,符合第一个断言预期的结果。所以根据第二个断言,我们可以很确定算出来的171就是问题中需要我们求的日子

目前的实现稍微有点繁琐,聪明的你能想出更简单的办法来吗?

Last updated