復活節最短原始碼分析

regonly1發表於2011-04-27
題目:
本週日,也就是4月24日,是今年的復活節,復活節(Easter),是紀念耶穌基督復活的節日,西方信基督教的國家都過這個節。
復活節的日期是不固定的在西方教會傳統裡,春分之後第一次滿月之後的第一個星期日即為復活節。東方教會則規定,如果滿月恰逢星期日,則復活節再推遲一週。 因此,節期大致在3月22日至4月25日之間。復活節是最古老最有意義的基督教節日之一,慶祝的是基督的復活,世界各地的基督徒每年都要舉行慶祝。

節日的演算法也不勞大家去找了

QUOTE:
現在有一個簡便的演算法供大家參考!

年份只限於1900年到2099年
1 設要求的那一年是Y年,從Y減去1900,其差記為N。
2 用19作除數去除N,餘數記為A。
3 用4作除數去除N,不管餘數,把商記為Q。
4 用19去除7A+1,把商記為B,不管餘數。
5 用29去除11A+4-B,餘數記為M。
6 用7去除N+Q+31-M,餘數記為W。
7 計算25-M-W。

得出答數即可定出復活節的日期。若為正數,月份為4月,如為負數,月份為3月。若為0,則為3 月31日。

現在提出比賽要求:
  • 用最短的程式碼,求出2011~2099之間的復活節,具體為:

    • Q1:2011~2099之間每一年的復活節的具體日期
    • Q2:2011~2099之間,幾月幾號出現得最多?3月出現得最多的是幾號?4月出現得最多的又是幾號?分別出現了多少次?
    • Q3:2011~2099之間的復活節,3月22日至4月25日之間哪些日子沒有出現過?
    • Q4:2011~2099之間“愚蛋節”的總次數,即4月1日同時又是復活節的總數量以及都在哪些年份出現。
  • 答案要求用Package給出,package名為easter:

    • package宣告為create or replace package easter
    • Q1的宣告為procedure showAllEasterDay,無引數,輸出為一列表,形式如下所示:
      YEAR     DAY
      2011    04-24
      2012    04-08
      …………
    • Q2的宣告為procedure showMaxOccurenceEasterDay,無引數,輸出為一行資料,日期後面跟著輸出出現的次數,如果是多個日期,則按從小到達,用“/”分隔顯示,形式如下所示:
      MAXOCC   MO_CNT        MAXOCC_3   MO3_CNT              MAXOCC_4          MO4_CNT   
      xx-xx                x1               03-xx                 x2            04-xx/04-yy                       x3
    • Q3的宣告為procedure showLeapEasterDay,無引數,連續未出現的日期用一行顯示,輸出為一列表,形式如下所示:
      ABSENT_START      ABSENT_END
      03-22                             03-25
      04-02                             04-02
      04-11                             04-22
      ………………(以上為示例資料,並非真實資料)
    • Q4的宣告為procedure showFoolEasterDay,無引數,輸出為一列表,形式如下所示(假設出現了n次):
      YEAR        TOTAL
      yyyy1            n
      yyyy2            n
      …………
      yyyyn            n
  • 包名和Q1~Q4的四個過程必須按約定給出,若需要在包中加入其他過程,請 自便,但需要注意,目標是原始碼位元組數最小哦!空格、TAB、CR、LF都不算位元組數,程式碼中不要包含註釋,註釋請寫在另外的檔案中,4月22日提交你的 結果,期間可在本帖隨意show你的程式碼的位元組數

##############################################################

本來提交的人不多,到後來每個選手的位元組數也越來越少,我的應該是1500位元組以內最多的了,不過在野花版主的鼓勵下,決定還是寫一寫。

解題分析:
1、所有型別都以varchar2(99)替代,包括int型別的轉換,都是用Oracle內部的轉換實現,使得所有型別都只以一個字元表達。

2、列印封裝為一個P函式

3、初始化時,做以下事情:
3.1、將所有復活節都以“yyyy  mm-dd”方式儲存在陣列v中,陣列v在package初始化時載入
3.2、獲取所有復活節在4月1日的年份,放入陣列r中
3.3、將每個日期最大的出現次數儲存在陣列u中,陣列以mm-dd為索引,存放相應出現的次數
3.4、將三月份、四月份以總共出現的次數最高的次數放到變數m/n/q中

4、列印:
4.1、showAllEasterDay:列印v陣列。
4.2、showMaxOccurenceEasterDay:從陣列u中輪詢,將每個日期出現次數為q/m/n的日期分別拼接起來,列印最終的字串d/e/f即相應的次數q/m/n。
4.3、showLeapEasterDay:這個是要列印沒有在指定區間的出現過的日期,如果是連續的則要求只輸出開始日期和結束日期。一開始很自然的想到了野花的連續號段處理,但是得用sql來實現。我想了下,預計用sql實現會比較長,所以直接考慮用pl/sql來實現。

----------------------------------------------------------------------
             我的演算法(位元組長度1442)
----------------------------------------------------------------------
        首先,我們知道,要列印的是一個開始時間和一個結束時間,如果只有一天,則結束時間=開始時間,所以先定義這兩個變數。

        然後,得到要求區間所有的日期(也就是3.22-4.25),透過 1 .. date'1-4-25' - date'1-3.22'+1,輪詢每個日期。

        再給定一個標識,指定如果當前日期在陣列中存在,則標識0,如果不存在,則標識1,然後到下一條記錄的時候,首先判斷當前日期是否存在於陣列中:
        A> 如果不存在,則將結束日期置為當前日期,並判斷標識是否為0,如果為0,則將開始日期也置為當前日期。並將標識置為1。
        B> 如果存在,則判斷標識是否為1,如果為1,則說明上個日期是出現在陣列中的,所以要列印開始和結束日期。然後將標識置為0。

可能邏輯說明有點枯燥,舉個例子吧:
比如說,有以下日期列表:
日期   是否存在陣列中(1表示不存在,因為實際要列印的是不存在的日期)
3.22   1  --此時3.22不在陣列中,所以置開始日期和結束日期為3.22,由於變數標
                  識flag初始化為0,所以此時不列印,然後置flag=1;
3.23   0  --此時3.23在陣列中,開始日期和結束日期還是3.22,但是此時flag=1,
                  所以此時列印,並置flag=0;
3.24   0  --此時3.23在陣列中,開始日期和結束日期還是3.22,但是此時flag=0,
                  所以此時不列印,並置flag=0;
3.25   1  --此時3.25不在陣列中,開始日期和結束日期被置為3.25,並且flag=0,
                  所以此時不列印,並置flag=1;
3.26   1  --此時3.26不在陣列中,開始日期仍為3.25,結束日期被置為3.26,此時
                  flag=1,所以此時仍不列印,並置flag=1;
3.27   1  --此時3.27不在陣列中,開始日期仍為3.25,結束日期被置為3.27,並且
                  flag=1,所以此時仍不列印,並置flag=1;
3.28   0  --此時3.28在陣列中,開始日期為3.25,結束日期為3.27,且flag=1,所
                  以此時列印開始和結束日期,並置flag=0;

        也就是每次滿足存在陣列中,而且上次存在標識為不存在,則列印變數中的開始和結束日期。

        如果不存在陣列中,則輪詢第一次首先將開始日期和結束日期置為當前輪詢日期,然後接下去日期還不存在於陣列中,則將結束日期置為當前日期,開始日期不變。直到輪詢到日期存在為止,然後按上個步驟判斷是否列印。

        特殊情況:當結束日期直到超過4.25時,還沒有出現存在的日期,就直接列印開始和結束日期。

    4.4、showFoolEasterDay:列印r陣列即可。

程式碼:
create or replace package body easter as
    subtype w is varchar2(99);

    type s is table of w index by w;
    u s;
    r s;
    v s;
    t w;
    q w := 0;
    m w := 0;
    n w := 0;
   
    a w;
    b w;
    c w;
    d w;
    e w;
    f w := 1900;

    procedure p(a w)as begin dbms_output.put_line(a); end;

    procedure showAllEasterDay as
    begin
        p('YEAR    DAY');
        for i in 1 .. v.count loop
            p(v(i));
        end loop;
    end;

    procedure showMaxOccurenceEasterDay as
        d  w;
        e  w;
        f   w;
    begin
        a := u.first;
        while a is not null loop
            b := u(a);--取每個日期的出現次數
            if b = q then --當該日期出現次數為最大出現次數時
                d := d || '/' || a;--將符合要求的日期拼接到字串中
            end if;
            if a like '03%' and b = m then--三月份出現最多的
                e := e || '/' || a;
            elsif b = n then--除了三月份,就是四月份了
                f := f || '/' || a;
            end if;
            a := u.next(a);
        end loop;

--列印時,沒去掉第一個/符號
        p('MAXOCC             MO_CNT  MAXOCC_3      MO3_CNT    MAXOCC_4     MO4_CNT    ');
        p(d || '  '  || q || '       ' ||
          e || '         '  || m  || '          '  ||
          f || '  '  || n);
    end;

    procedure showLeapEasterDay as
    begin
        f := 0;
        p('ABSENT_START  ABSENT_END');
        for i in 0 .. date'1-4-25' - date'1-3-22' + 1 loop
            a := to_char(date'1-3-22' + i, 'mm-dd');
            if u.exists(a) then--日期存在
                if f = 1 then
                    p(b || '          ' || e);--上次不存在則列印
                end if;
                f := 0;--置本次存在標識為0,即存在
            else
                e := a;--結束日期=當前日期
                if f = 0 then
                    b := a;--如果上次存在,則開始日期=當前日期
                end if;
                if a = '04-25' then--到4.25還沒有存在的日期就直接列印
                    p(b || '          ' || e);
                end if;
                f := 1;--置存在標識為1,即不存在
            end if;
        end loop;
    end;

    procedure showFoolEasterDay as
    begin
        p('YEAR    TOTAL');
        for i in 1 .. r.count loop
            p(r(i) || '     ' || t);
        end loop;
    end;

begin
    u.delete;
    t := 0;
    for i in f .. 2099 loop
        d := i - f;
        a := d mod 19;
        c := (11 * a + 4 - trunc((7 * a + 1)/19)) mod 29;
        b := 25-c-mod(d + trunc(d/4) + 31 - c, 7);

        if b > 0
        then a := '04'; c := b;
        else a := '03'; c := 31+b;
        end if;
        c := lpad(c,2,0);

        b := a||'-'||c;
        v(d+1) := i || '    ' || b;
        if b = '04-01' then t := t + 1; r(t) := i;  end if;
        if u.exists(b) then
            e := u(b) + 1;--累計每個日期的出現次數
            q := greatest(e,q);--取最大次數演算法
            if b like '03%' then--取三月份最大次數演算法
                m := greatest(e,m);
            else--四月份最大次數演算法
                n := greatest(e,n);
            end if;
        else e := 1;
        end if;
        u(b) := e;
    end loop;
end;
/

最後,為了照顧到程式碼的完整性,可以將開始年份和結束年份變成以引數輸入。這個就不再貼出程式碼了,改動很簡單。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12932950/viewspace-693846/,如需轉載,請註明出處,否則將追究法律責任。

相關文章