高斯日記算法

天才少年

可能大部分同窗都據說過一個知名的故事。一位小學老師,爲了讓同窗們中止吵鬧,給出了一道數據題    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到了嗎?

相關文章
相關標籤/搜索