复活节最短源代码分析

题目:
本周日,也就是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/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/12932950/viewspace-693846/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值