R 菜鸟生活篇 复活节到底是哪一天

地球上的人类爱过节,男人可以趁机放假,女人可以借机购物。人类最重要的节日,在东方当属春节、中国国庆节和劳动节,在西方当属圣诞节、元旦和复活节。前五个都好说,阳历或阴历上的日期是确定的,那么,聪明的你,我提个问题:最后那个,就是复活节,是在哪一天?

你回答说:

复活节是每年春分月圆之后第一个星期日。

OK。那么,再问:那它到到到到到底是哪一天……

三个关键词:春分,这是阳历;月圆,这是阴历;星期日,这是不阴不阳的历。敢问这是哪位高人定的,是在考验人类的计算水平吗?够狠。

一查才知道,的确是在考计算。英文的 computer 是“计算机”,compute 和 computation 分别是动词和名词的“计算”,他们均来自拉丁语的Computus,而这个词的本意就是“计算复活节的方法”!

我又问了:为啥是这样规定呢?

你说:

春分,是因为此后北半球开始日长夜短,意味着光明;月圆,是因为夜晚也洒满光辉,也意味着光明;星期日,是因为这天大家都休息,不用倒休。

我脑海中立刻浮现出明教、日月神教等字眼,嘴上只好说,好吧,算你有理。我算是勉强被说服了,但是听到你的下一句话,我崩溃了,你说:

只不过啊,天主教和东正教的复活节是两种不同的计算方法……

基督教有三大主要派别,其中天主教和基督新教(华语圈常简称基督教)属于西方罗马教会,使用公历;东正教属于东方教会,使用儒略历。

我不信教,这已经超出我大脑能理解的极限了。没啥说的,请 R 出马,自己写个自定义函数,看看这个 Computus 是怎么计算的吧。计算方法可以选择高斯还是米乌斯,日历可以选择公历还是儒略历。因此,整个函数的结构是两个大分支下各有两个小分支。

computus  <- function(year = 2013, method = "Meeus", calendar = "Gregorian")
{
  Y <- year
  # 高斯算法
  if (method == "Gauss") # 适用于 1583 - 2299 年
  {
# 公历(天主教)
    if (calendar == "Gregorian")
    {
      k <- (Y - 1500) %/% 100 + 1
      M <- c(22,22,23,23,24,24,24,25)[k]
      N <- c(2,2,3,4,5,5,6,0)[k]
      
    } else if (calendar == "Julian")
# 儒略历(东正教)
    {
      M <- 15; N <- 6
    } else
    {
      print("sorry, the calendar does not exist. use method = 'Gregorian' or 'Julian'")
    }
    a <- Y %% 19
    b <- Y %% 4
    c <- Y %% 7
    d <- (19 * a + M) %% 30
    e <- (2 * b + 4 * c + 6 * d + N) %% 7
    computus.date <- ifelse(d + e < 10,
                     paste("3-", d + e + 22, sep = ""),
                     paste("4-", d + e - 9, sep = ""))
    if (computus.date == "4-26") computus.date <- "4-19"
    if (computus.date == "4-25" & d == 28 & e == 6 & a > 10) computus.date <- "4-19" # 高斯,I 服了 U!
    computus <- paste(Y, computus.date, sep = "-")
  } else if (method == "Meeus")
# Meeus 算法
  {
    # 公历(天主教)
    if (calendar == "Gregorian")
    {
      a <- Y %% 19
      b <- Y %/% 100
      c <- Y %% 100
      d <- b %/%4
      e <- b %%4
      f <- (b + 8) %/% 25
      g <- (b - f + 1) %/%3
      h <- (19 * a + b - d - g + 15) %% 30
      i <- c %/% 4
      k <- c %% 4
      L <- (32 + 2 * e + 2 * i - h - k) %% 7
      m <- (a + 11 * h + 22 * L) %/% 451
      month <- (h + L - 7 * m + 114) %/% 31
      day <- ((h + L - 7 * m + 114) %% 31) + 1
    } else if (calendar == "Julian")
      # 儒略历(东正教)
    {
      a <- Y %% 4
      b <- Y %% 7
      c <- Y %% 19
      d <- (19 * c + 15) %% 30
      e <- (2 * a + 4 * b - d + 34) %% 7
      month <- (d + e + 114) %/% 31
      day <- ((d + e + 114) %% 31) + 1
    } else
    {
      print("sorry, the calendar does not exist. use method = 'Gregorian' or 'Julian'")
    }
    computus <- paste(Y, month, day, sep = "-")
  } else
  {
    print("sorry, the method does not exist. use method = 'Gauss' or 'Meeus'")
  }
  return(computus)
}

这个自定义函数使用的时候 method 可以选择 Gauss 或 Meeus 算法,日历可以选择 Gregorian 公历或者 Julian 儒略历。

让我们试试这个自定义函数的运算结果:

computus()
## [1] "2013-3-31"
computus(year = 2013, method = "Gauss")
## [1] "2013-3-31"
computus(year = 1961)
## [1] "1961-4-2"
computus(year = 2000)
## [1] "2000-4-23"

都是正确的。

下面让我们计算出本世纪所有年份的天主教复活节:

computus(year = 2000:2099)
##   [1] "2000-4-23" "2001-4-15" "2002-3-31" "2003-4-20" "2004-4-11"
##   [6] "2005-3-27" "2006-4-16" "2007-4-8"  "2008-3-23" "2009-4-12"
##  [11] "2010-4-4"  "2011-4-24" "2012-4-8"  "2013-3-31" "2014-4-20"
##  [16] "2015-4-5"  "2016-3-27" "2017-4-16" "2018-4-1"  "2019-4-21"
##  [21] "2020-4-12" "2021-4-4"  "2022-4-17" "2023-4-9"  "2024-3-31"
##  [26] "2025-4-20" "2026-4-5"  "2027-3-28" "2028-4-16" "2029-4-1" 
##  [31] "2030-4-21" "2031-4-13" "2032-3-28" "2033-4-17" "2034-4-9" 
##  [36] "2035-3-25" "2036-4-13" "2037-4-5"  "2038-4-25" "2039-4-10"
##  [41] "2040-4-1"  "2041-4-21" "2042-4-6"  "2043-3-29" "2044-4-17"
##  [46] "2045-4-9"  "2046-3-25" "2047-4-14" "2048-4-5"  "2049-4-18"
##  [51] "2050-4-10" "2051-4-2"  "2052-4-21" "2053-4-6"  "2054-3-29"
##  [56] "2055-4-18" "2056-4-2"  "2057-4-22" "2058-4-14" "2059-3-30"
##  [61] "2060-4-18" "2061-4-10" "2062-3-26" "2063-4-15" "2064-4-6" 
##  [66] "2065-3-29" "2066-4-11" "2067-4-3"  "2068-4-22" "2069-4-14"
##  [71] "2070-3-30" "2071-4-19" "2072-4-10" "2073-3-26" "2074-4-15"
##  [76] "2075-4-7"  "2076-4-19" "2077-4-11" "2078-4-3"  "2079-4-23"
##  [81] "2080-4-7"  "2081-3-30" "2082-4-19" "2083-4-4"  "2084-3-26"
##  [86] "2085-4-15" "2086-3-31" "2087-4-20" "2088-4-11" "2089-4-3" 
##  [91] "2090-4-16" "2091-4-8"  "2092-3-30" "2093-4-12" "2094-4-4" 
##  [96] "2095-4-24" "2096-4-15" "2097-3-31" "2098-4-20" "2099-4-12"

当我敲完上面几十行代码后,突然有些异样的感觉。上网搜了一下,悲催了,原来上面那些自定义函数其实都白敲了,复活节计算从头到尾只需要两行代码:

require("timeDate")
Easter()

每到这个时候,我都会深入理解一次,R 的精髓在于包啊!

注:本文是受网友 yangliufr 的启发写成的。征得 yangliufr 同学的同意后,本打算在 yangliufr 同学所写的代码基础上修改,后来发现 yangliufr 使用的百度百科的计算方法出处不详,所以重新写了代码。在此对 yangliufr 致谢。

注:计算方法参考维基百科

原文链接

comments powered by Disqus