十二生肖、干支紀年法與Go語言編程 | 不輟集

十二生肖、干支紀年法與Go語言編程

目錄
  1. 1. 十二生肖
  2. 2. 干支紀年法
  3. 3. 問題

如無特殊說明,本文標音採用甲子話拼音方案。甲子話系陸豐市甲子鎮通行的語言,屬閩南語潮汕話三甲片。

十二生肖

十二生肖本地讀音爲:

  • 鼠牛虎兔 /cu² ngu⁵ hao² tao³/
  • 龍蛇馬羊 /lêng⁵ zua⁵ bhê² ion⁵/
  • 猴雞狗豬 /gao⁵ goi¹ gao² du¹/

干支紀年法

「干」是天干,有 10:

  • 甲乙丙丁 /gah⁴ ig⁴ bian² dêng¹/
  • 戊己庚辛 /bhao⁷ gi² gên¹ sing¹/
  • 壬癸 /rim⁶ gui³/

「支」是地支,有 12:

  • 子丑寅卯 /zu² tiu² ing⁵ bhao²/
  • 辰巳午未 /sing⁵ zi⁶ ngao² bhi⁷/
  • 申酉戌亥 /sing¹ iu² sug⁴ hai⁶/

天干從甲開始,地支從子開始,天干地支相配形成 60 種組合,用來紀年。從甲子出發,60 年後又回到甲子,因此稱 60 年爲「一甲子」。

十二地支與十二生肖相對應,因此也用生肖紀年。如甲子年,地支爲「子」,對應生肖「鼠」,因此甲子年也稱之為「鼠年」。

問題

問題1:已知 2020 年是鼠年,請問 2021 年是什麼年?

排在鼠之後的生肖是牛,因此 2021 年是牛年。

問題2:已知 2020 年是庚子年 /gên¹ zu² ni⁵/,請問 2021 年是什麼年?

庚之後爲辛,子之後爲丑,因此 2021 年是辛丑年 /sing¹ tiu² ni⁵/。

問題3:已知 1024 年是甲子年,問最近過去的甲子年和將要到來的甲子年是公元多少年?

使用 Go 語言解決,編碼如下:

func f1() {
jz := 1024
for {
jz = jz + 60
if jz > 2020 {
println(jz-60, jz)
break
}
}
}

執行後輸出結果爲:

1984 2044

因此,最近過去的甲子年是 1984 年,將要到來的甲子年是 2044 年。

問題4:已知干支紀年法中有 10 天干,12 地支,有 60 種合法組合,求所有非法的組合,並探討其中規律。

先說結論:

  1. 所有非法的組合爲:
    甲丑 甲卯 甲巳 甲未 甲酉 甲亥 乙子 乙寅 乙辰 乙午 乙申 乙戌 丙丑 丙卯 丙巳 丙未 丙酉 丙亥 丁子 丁寅 丁辰 丁午 丁申 丁戌 戊丑 戊卯 戊巳 戊未 戊酉 戊亥 己子 己寅 己辰 己午 辛寅 辛辰 辛午 辛申 辛戌 壬丑 壬卯 壬巳 壬未 壬酉 壬亥 癸子 癸寅 癸辰 癸午 癸申 癸戌
  2. 其中的規律是:使用 1~10 給天干編號,1~12給地支編號,分別從天干和地支的編號中任取一個,如果其和爲偶數則是合法的組合,否則就是非法的組合。例如「甲丑」中「甲」的編號是 1,「丑」的編號是 2,1+2=3 爲奇數,是非法的組合。

試著使用編程來解決此問題。

首先,將天干和地址的字符串定義如下:

const gs = "甲乙丙丁戊己庚辛壬癸"
const zs = "子丑寅卯辰巳午未申酉戌亥"

第一種思路是:先求出所有合法的組合,然後窮盡所有組合,如組合不在合法組合中即是非法組合,輸出即可,編程到函數 f1 如下:

func f1() {
// 天干數組
garr := []rune(gs)
// 地支數組
zarr := []rune(zs)
// 保存合法的干支組合的Map
// Key爲干支,Value爲天干索引與地支索引之和
tdmap := make(map[string]int)

// 將干支組合保存到Map中,直到出現第一個重複項就停止遍歷
for i, j := 0, 0; i < len(garr) && j < len(zarr); i, j = (i+1)%len(garr), (j+1)%len(zarr) {
k := string(garr[i]) + string(zarr[j])
if _, ok := tdmap[k]; ok {
break
}
tdmap[k] = i + j
}

// 輸出所有合法的組合
println("\n所有合法的組合:")

for k, v := range tdmap {
fmt.Printf("%s:%d ", k, v)
}

// 輸出所有非法的組合
println("\n所有非法的組合:")
for i := 0; i < len(garr); i++ {
for j := 0; j < len(zarr); j++ {
k := string(garr[i]) + string(zarr[j])
if _, ok := tdmap[k]; !ok {
fmt.Printf("%s:%d ", k, i+j)
}
}
}
}

輸出結果爲:

所有合法的組合:
庚午:12 己卯:8 甲申:8 己酉:14 丙寅:4 丁卯:6 己巳:10 癸酉:18 戊寅:6 戊戌:14 癸巳:14 癸卯:12 甲辰:4 乙卯:4 辛巳:12 丙戌:12 壬辰:12 乙未:8 辛亥:18 戊午:10 丁巳:8 己未:12 辛 壬申:16 甲午:6 庚子:6 辛丑:8 庚戌:16 丙辰:6 戊辰:8 癸未:16 甲子:0 丙申:10 丁未:10 壬子:8 庚辰:10 甲戌:10 丁丑:4 壬午:14 丙子:2 庚寅:8 壬寅:10 戊申:12 甲寅:2 乙丑:2 辛未:14 丁亥:14 戊子:4 己亥:16 丙午:8 庚申:14 癸亥:20 丁酉:12
所有非法的組合:
甲丑:1 甲卯:3 甲巳:5 甲未:7 甲酉:9 甲亥:11 乙子:1 乙寅:3 乙辰:5 乙午:7 乙申:9 乙戌:11 丙丑:3 丙卯:5 丙巳:7 丙未:9 丙酉:11 丙亥:13 丁子:3 丁寅:5 丁辰:7 丁午:9 丁申:11 丁 己辰:9 己午:11 己申:13 己戌:15 庚丑:7 庚卯:9 庚巳:11 庚未:13 庚酉:15 庚亥:17 辛子:7 辛寅:9 辛辰:11 辛午:13 辛申:15 辛戌:17 壬丑:9 壬卯:11 壬巳:13 壬未:15 壬酉:17 壬亥:19 癸子:9 癸寅:11 癸辰:13 癸午:15 癸申:17 癸戌:19

嗯,符合預期,合法的組合和非法的組合各佔 60 個。

觀察發現,所有合法的組合的Value值都是偶數,所有非法的組合的Value值都是奇數。

因此有第二種思路求出非法組合:

func f2() {
garr := []rune(gs)
zarr := []rune(zs)

// 輸出所有非法的組合
println("\n所有非法的組合:")
for i := 0; i < len(garr); i++ {
for j := 0; j < len(zarr); j++ {
if (i+j)%2 != 0 {
fmt.Printf("%s:%d ", string(garr[i])+string(zarr[j]), i+j)
}
}
}
}

輸出結果:

所有非法的組合:
甲丑:1 甲卯:3 甲巳:5 甲未:7 甲酉:9 甲亥:11 乙子:1 乙寅:3 乙辰:5 乙午:7 乙申:9 乙戌:11 丙丑:3 丙卯:5 丙巳:7 丙未:9 丙酉:11 丙亥:13 丁子:3 丁寅:5 丁辰:7 丁午:9 丁申:11 丁 己辰:9 己午:11 己申:13 己戌:15 庚丑:7 庚卯:9 庚巳:11 庚未:13 庚酉:15 庚亥:17 辛子:7 辛寅:9 辛辰:11 辛午:13 辛申:15 辛戌:17 壬丑:9 壬卯:11 壬巳:13 壬未:15 壬酉:17 壬亥:19 癸子:9 癸寅:11 癸辰:13 癸午:15 癸申:17 癸戌:19

很棒,跟思路一的非法組合一致。

學過小學數學的都知道,奇數+偶數=奇數,奇數+奇數=偶數,偶數+偶數=偶數。於是有了第三種思路,只要保證 i 和 j 的奇偶性不同即可保證 i 和 j 的組合爲非法組合。代碼如下:

func f3() {
garr := []rune(gs)
zarr := []rune(zs)

// 輸出所有非法的組合
println("\n所有非法的組合:")
for i := 0; i < len(garr); i++ {
if i%2 == 0 {
for j := 1; j < len(zarr); j = j + 2 {
fmt.Printf("%s:%d ", string(garr[i])+string(zarr[j]), i+j)
}
} else {
for j := 0; j < len(zarr); j = j + 2 {
fmt.Printf("%s:%d ", string(garr[i])+string(zarr[j]), i+j)
}
}
}
}

輸出結果同思路二的一致。

完結