高斯日记算法

天才少年

可能大部分同窗都据说过一个知名的故事。一位小学老师,为了让同窗们中止吵闹,给出了一道数据题    1+2+3+…+100 = ?  本来觉得可让他们安静二三十分钟,结果1分钟不到,就有一个小朋友举手回答了出来,老师漫不经心的看了一眼答案,万万没想到居然是正确的。api

这个小朋友只有9岁,他就是高斯。函数

 

日记里的秘密

高斯有个好习惯:不管如何都要记日记。他的日记有个不同凡响的地方,他从不注明年月日,而是用一个整数代替,好比:4210工具

后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光能够用于浪费呢?源码分析

高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记上标注着:5343,所以可算出那天是:1791年12月15日。高斯得到博士学位的那天日记上标着:8113 。如今请算出高斯得到博士学位的年月日?测试

 

解题思路

咱们来抽象一下问题,本质上是要求取某年某月某往后的n天,是哪年哪月哪日?this

方案一:循环遍历法spa

比较容易想到的就是遍历的方式,循环n次,就能够推导出n天后的日期了,咱们来尝试一下。设计

public static void calc(int year, int month, int day, int n) {

        for (int i = 0; i < n; i++) {
// 日子一每天过 day
++;
// 若是过到月底,须要把日期重置,月份+1
if(day > getMonthLastDay(year,month)){ day = 1; month++;
// 若是过到年末,须要把月份和日期同时重置,年份+1
if(month >12){
month
= 1; day = 1; year++; } } } System.out.println( n + "天后的日期为:"+year + "-" + month + "-" +day); }

先把已知条件带入,在main函数中执行  calc(1777,4,30,5343); code

能够获得结果为1791年12月16日,和题目上的1791年12月15日相差一天,这是什么缘由呢?blog

细想能够察觉,高斯生日的这一天其实被算做了第一天的,也就是咱们带入方法时,须要传入1777年4月29日

     calc(1777,4,29,5343);  的运行结果是   1791年12月15日

而  calc(1777,4,29,8113);   的运行结果是   1799年7月16日,能够想见高斯在22岁时就已得到博士学位了,真正的年少得志。

 

方案二:善用工具法

jdk8给咱们提供了一系列很友好的日期api,让咱们求解此类问题时,能够快速拿到答案。

// 建立一个高斯生日前一天的日期
LocalDate date = LocalDate.of(1777,4,29);
// 调用增长天数的方法,可以直接得到n天后的日期 System.out.println(date.plusDays(
8113));

 

方案比较

 既然有咱们本身来书写的方案,还有jdk提供的方案,那么问题来了,哪一种方案执行更快、效率更好呢?

long start = System.currentTimeMillis();
calc(1777, 4, 29, 8113);
long cost = System.currentTimeMillis() - start;
System.out.println("耗时" + cost);

System.out.println("===========");

start = System.currentTimeMillis();
//日期工具 jdk8 joda time
LocalDate date = LocalDate.of(1777, 4, 29);
System.out.println(date.plusDays(8113));
cost = System.currentTimeMillis() - start;
System.out.println("耗时" + cost);

咱们经过毫秒计算工具来测试一下,获得结果以下

 注意,这里的时间单位是ms,两种方案实际相差为0.1s左右。

大跌你的眼镜吧,咱们本身写的实现居然比jdk提供的实现方式快,这是为何呢?

 

源码分析

咱们深刻LocalDate类的实现看一下,和咱们本身实现的有何区别?

    public LocalDate plusDays(long daysToAdd) {
        if (daysToAdd == 0) {
            return this;
        }
// addExact就是一个简单的加法运算
// toEpochDay() 其中epoch表明的是元年,计算机元年是1970年1月1日,这里计算的是当前日期距离元年的天数
// 当前日期距离元年的天数 + 当前日期事后的n天 = n天后距离元年的天数
long mjDay = Math.addExact(toEpochDay(), daysToAdd);
// ofEpochDay(int n) 是计算距离元年n天的日期
return LocalDate.ofEpochDay(mjDay); }

再点击进 toEpochDay() 看一下

public long toEpochDay() {
        long y = year;
        long m = month;
        long total = 0;
        total += 365 * y;
        if (y >= 0) {
            total += (y + 3) / 4 - (y + 99) / 100 + (y + 399) / 400;
        } else {
            total -= y / -4 - y / -100 + y / -400;
        }
        total += ((367 * m - 362) / 12);
        total += day - 1;
        if (m > 2) {
            total--;
            if (isLeapYear() == false) {
                total--;
            }
        }
        return total - DAYS_0000_TO_1970;
    }

能够看到,这个方法都是经过公式来计算的,而 ofEpochDay() 也一样如此,因此咱们能够简单把工具法等价为公式法。

 

总结

为何循环法比公式法还快呢,咱们拉长一下这个问题。当要计算更多天数以后的日期时,二者的表现如何呢?

咱们计算 8113333 天后的日期,能够发现的循环法耗时和公式法耗时基本一致,都在0.1s左右。再增长到81133333 天后呢,能够明显的看到公式法仍然保持在0.1s,而循环法的耗时变为1s以上,大幅提升。

这说明在计算次数增长以后,循环法的耗时会成斜线增加,而公式法的耗时基本保持在水平直线上,这也是jdk的设计者们所作的权衡。对咱们自身来说,不一样的应用场景下,能够选择不一样的实现方式,没有最好的,只有最适合的,你get到了吗?

相关文章
相关标签/搜索