Chinese zodiac

From Rosetta Code
Task
Chinese zodiac
You are encouraged to solve this task according to the task description, using any language you may know.

Determine the Chinese zodiac sign and related associations for a given year.

Traditionally, the Chinese have counted years using two simultaneous cycles, one of length 10 (the "celestial stems") and one of length 12 (the "terrestrial branches"); the combination results in a repeating 60-year pattern. Mapping the branches to twelve traditional animal deities results in the well-known "Chinese zodiac", assigning each year to a given animal. For example, Saturday, January 28, 2017 CE (in the common Gregorian calendar) begins the lunisolar year of the Rooster.

The celestial stems have no one-to-one mapping like that of the branches to animals; however, the five pairs of consecutive stems each belong to one of the five traditional Chinese elements (Wood, Fire, Earth, Metal, and Water). Further, one of the two years within each element's governance is associated with yin, the other with yang.

Thus, 2017 is also the yin year of Fire. Note that since 10 and 12 have the same parity, the association between animals and yin/yang doesn't change. Consecutive Years of the Rooster will cycle through the five elements, but will always be yin, despite the mismatch between the male animals and the female aspect.

Task
Create a subroutine or program that will return or output the animal, yin/yang association, and element for the lunisolar year that begins in a given CE year.

You may optionally provide more information in the form of the year's numerical position within the 60-year cycle and/or its actual Chinese stem-branch name (in Han characters or Pinyin transliteration).

Requisite information
  • The animal cycle runs in this order: Rat, Ox, Tiger, Rabbit, Dragon, Snake, Horse, Goat, Monkey, Rooster, Dog, Pig.
  • The element cycle runs in this order: Wood, Fire, Earth, Metal, Water.
  • The yang year precedes the yin year within each element.
  • The current 60-year cycle began in 1984 CE.

Thus, 1984 was the year of the Wood Rat (yang), 1985 was the year of the Wood Ox (yin), and 1986 the year of the Fire Tiger (yang).

Information for optional task
  • The ten celestial stems are jiă, , bĭng, dīng, , , gēng, xīn, rén, and gŭi. With the ASCII version of Pinyin tones, the names are written "jia3", "yi3", "bing3", "ding1", "wu4", "ji3", "geng1", "xin1", "ren2", and "gui3".
  • The twelve terrestrial branches are , chŏu, yín, măo, chén, , , wèi, shēn, yŏu, , hài. In ASCII Pinyin, those are "zi3", "chou3", "yin2", "mao3", "chen2", "si4", "wu3", "wei4", "shen1", "you3", "xu1", and "hai4".

Therefore 1984 was 甲子 (jiă-zĭ, or jia3-zi3). 2017 is the 34th year of the current cycle, 丁酉 (dīng-yŏu or ding1-you3).


Common Lisp

Translation of: Ruby

<lang lisp>; Any CE Year that was the first of a 60-year cycle (defconstant base-year 1984)

(defconstant celestial-stems

 '("甲" "乙" "丙" "丁" "戊" "己" "庚" "辛" "壬" "癸"))

(defconstant terrestrial-branches

 '("子" "丑" "寅" "卯" "辰" "巳" "午" "未" "申" "酉" "戌" "亥"))

(defconstant zodiac-animals

 '("Rat"   "Ox"   "Tiger"  "Rabbit"  "Dragon" "Snake"
   "Horse" "Goat" "Monkey" "Rooster" "Dog"    "Pig"))

(defconstant elements '("Wood" "Fire" "Earth" "Metal" "Water"))

(defconstant aspects '("yang" "yin"))

(defconstant pinyin

 (pairlis (append celestial-stems terrestrial-branches)
   '("jiă" "yĭ" "bĭng" "dīng" "wù" "jĭ" "gēng" "xīn" "rén" "gŭi"
     "zĭ" "chŏu" "yín" "măo" "chén" "sì" "wŭ" "wèi" "shēn" "yŏu" "xū" "hài")))

(defun this-year () (nth 5 (multiple-value-list (get-decoded-time))))

(defun pinyin-for (han) (cdr (assoc han pinyin :test #'string=)))

(defun chinese-zodiac (&rest years)

(loop for ce-year in (if (null years) (list (this-year)) years) collecting
  (let* ((cycle-year (- ce-year base-year))
         (stem-number (mod cycle-year 10))
         (stem-han    (nth stem-number celestial-stems))
         (stem-pinyin (pinyin-for stem-han))
         (element-number (floor stem-number 2))
         (element        (nth element-number elements))
         (branch-number (mod cycle-year 12))
         (branch-han    (nth branch-number terrestrial-branches))
         (branch-pinyin (pinyin-for branch-han))
         (zodiac-animal (nth branch-number zodiac-animals))
         (aspect-number (mod cycle-year 2))
         (aspect        (nth aspect-number aspects)))
         (cons ce-year (list stem-han branch-han stem-pinyin branch-pinyin element zodiac-animal aspect)))))

(defun get-args ()

 (or
  #+CLISP *args*
  #+SBCL (cdr *posix-argv*)
  #+LISPWORKS system:*line-arguments-list*
  #+CMU extensions:*command-line-words*
  nil))

(loop for cz in (apply #'chinese-zodiac (mapcar #'read-from-string (get-args)))

doing
 (format t "~{~a: ~a~a (~a-~a, ~a ~a; ~a)~%~}" cz))</lang>
Output:
$ sbcl --script cz.cl
2017: 丁酉 (dīng-yŏu, Fire Rooster; yin)
$ clisp cz.cl  193{5,8} 194{1,7} 1968 197{2,6}
1935: 乙亥 (yĭ-hài, Wood Pig; yin)
1938: 戊寅 (wù-yín, Earth Tiger; yang)
1941: 辛巳 (xīn-sì, Metal Snake; yin)
1947: 丁亥 (dīng-hài, Fire Pig; yin)
1968: 戊申 (wù-shēn, Earth Monkey; yang)
1972: 壬子 (rén-zĭ, Water Rat; yang)
1976: 丙辰 (bĭng-chén, Fire Dragon; yang)

Perl 6

Works with: Rakudo version 2017.01
Translation of: Ruby

<lang perl6>sub Chinese-zodiac ( Int $year ) {

   my @heaven  = <甲 jiă 乙 yĭ 丙 bĭng 丁 dīng 戊 wù 己 jĭ 庚 gēng 辛 xīn 壬 rén 癸 gŭi>.pairup;
   my @earth   = <子 zĭ 丑 chŏu 寅 yín 卯 măo 辰 chén 巳 sì 午 wŭ 未 wèi 申 shēn 酉 yŏu 戌 xū 亥 hài>.pairup;
   my @animal  = <Rat Ox Tiger Rabbit Dragon Snake Horse Goat Monkey Rooster Dog Pig>;
   my @element = <Wood Fire Earth Metal Water>;
   my @aspect  = <yang yin>;
   my $cycle_year = ($year - 4) % 60;
   my $i2         = $cycle_year % 2;
   my $i10        = $cycle_year % 10;
   my $i12        = $cycle_year % 12;
   %(
       'Han'     => @heaven[$i10].key ~ @earth[$i12].key,
       'pinyin'  => @heaven[$i10].value ~ @earth[$i12].value,
       'heaven'  => @heaven[$i10],
       'earth'   => @earth[$i12],
       'element' => @element[$i10 div 2],
       'animal'  => @animal[$i12],
       'aspect'  => @aspect[$i2],
       'cycle'   => $cycle_year + 1
   )

}

  1. TESTING

printf "%d: %s (%s, %s %s; %s - year %d in the cycle)\n",

   $_, .&Chinese-zodiac<Han pinyin element animal aspect cycle>
   for 1935, 1938, 1968, 1972, 1976, 1984, Date.today.year;</lang>
Output:
1935: 乙亥 (yĭhài, Wood Pig; yin - year 12 in the cycle)
1938: 戊寅 (wùyín, Earth Tiger; yang - year 15 in the cycle)
1968: 戊申 (wùshēn, Earth Monkey; yang - year 45 in the cycle)
1972: 壬子 (rénzĭ, Water Rat; yang - year 49 in the cycle)
1976: 丙辰 (bĭngchén, Fire Dragon; yang - year 53 in the cycle)
1984: 甲子 (jiăzĭ, Wood Rat; yang - year 1 in the cycle)
2017: 丁酉 (dīngyŏu, Fire Rooster; yin - year 34 in the cycle)

PowerShell

Translation of: Ruby

<lang PowerShell> function Get-ChineseZodiac {

   [CmdletBinding()]
   [OutputType([PSCustomObject])]
   Param
   (
       [Parameter(Mandatory=$false,
                  ValueFromPipeline=$true,
                  ValueFromPipelineByPropertyName=$true,
                  Position=0)]
       [ValidateRange(1,9999)]
       [int]
       $Year = (Get-Date).Year
   )
   Begin
   {
       $pinyin = @{
           '甲' = 'jiă'
           '乙' = 'yĭ'
           '丙' = 'bĭng'
           '丁' = 'dīng'
           '戊' = 'wù'
           '己' = 'jĭ'
           '庚' = 'gēng'
           '辛' = 'xīn'
           '壬' = 'rén'
           '癸' = 'gŭi'
           '子' = 'zĭ'
           '丑' = 'chŏu'
           '寅' = 'yín'
           '卯' = 'măo'
           '辰' = 'chén'
           '巳' = 'sì'
           '午' = 'wŭ'
           '未' = 'wèi'
           '申' = 'shēn'
           '酉' = 'yŏu'
           '戌' = 'xū'
           '亥' = 'hài'
       }
       $celestial   = '甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'
       $terrestrial = '子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'
       $animals     = 'Rat', 'Ox', 'Tiger', 'Rabbit', 'Dragon', 'Snake', 'Horse', 'Goat', 'Monkey', 'Rooster', 'Dog', 'Pig'
       $elements    = 'Wood', 'Fire', 'Earth', 'Metal', 'Water'
       $aspects     = 'yang', 'yin'
       $base = 4
   }
   Process
   {
       foreach ($ce_year in $Year)
       {
           $cycle_year     = $ce_year - $base
           $stem_number    = $cycle_year % 10
           $stem_han       = $celestial[$stem_number]
           $stem_pinyin    = $pinyin[$stem_han]
           $element_number = [Math]::Floor($stem_number / 2)
           $element        = $elements[$element_number]
           $branch_number  = $cycle_year % 12
           $branch_han     = $terrestrial[$branch_number]
           $branch_pinyin  = $pinyin[$branch_han]
           $animal         = $animals[$branch_number]
           $aspect_number  = $cycle_year % 2
           $aspect         = $aspects[$aspect_number]
           $index          = $cycle_year % 60 + 1
           [PSCustomObject]@{
               Year        = $Year
               Element     = $element
               Animal      = $animal
               Aspect      = $aspect
               YearOfCycle = $index
               ASCII       = "$stem_pinyin-$branch_pinyin"
               Chinese     = "$stem_han$branch_han"
           }
       }
   }

} </lang> <lang PowerShell> 1935, 1938, 1968, 1972, 1976 | Get-ChineseZodiac | Format-Table </lang>

Output:
Year Element Animal Aspect YearOfCycle ASCII     Chinese
---- ------- ------ ------ ----------- -----     -------
1935 Wood    Pig    yin             12 yĭ-hài    乙亥     
1938 Earth   Tiger  yang            15 wù-yín    戊寅     
1968 Earth   Monkey yang            45 wù-shēn   戊申     
1972 Water   Rat    yang            49 rén-zĭ    壬子     
1976 Fire    Dragon yang            53 bĭng-chén 丙辰     

Defaults to the current year: <lang PowerShell> Get-ChineseZodiac | Format-Table </lang>

Output:
Year Element Animal  Aspect YearOfCycle ASCII    Chinese
---- ------- ------  ------ ----------- -----    -------
2017 Fire    Rooster yin             34 dīng-yŏu 丁酉     

Ruby

This is written as a command-line tool that takes a list of CE year numbers as arguments and outputs their information; if no arguments are supplied, it displays the information for the current year.

<lang ruby># encoding: utf-8 pinyin = {

 '甲' => 'jiă',
 '乙' => 'yĭ',
 '丙' => 'bĭng',
 '丁' => 'dīng',
 '戊' => 'wù',
 '己' => 'jĭ',
 '庚' => 'gēng',
 '辛' => 'xīn',
 '壬' => 'rén',
 '癸' => 'gŭi',
 '子' => 'zĭ',
 '丑' => 'chŏu',
 '寅' => 'yín',
 '卯' => 'măo',
 '辰' => 'chén',
 '巳' => 'sì',
 '午' => 'wŭ',
 '未' => 'wèi',
 '申' => 'shēn',
 '酉' => 'yŏu',
 '戌' => 'xū',
 '亥' => 'hài'

} celestial = %w(甲 乙 丙 丁 戊 己 庚 辛 壬 癸) terrestrial = %w(子 丑 寅 卯 辰 巳 午 未 申 酉 戌 亥) animals = %w(Rat Ox Tiger Rabbit Dragon Snake

                  Horse Goat Monkey Rooster Dog    Pig)

elements = %w(Wood Fire Earth Metal Water) aspects = %w(yang yin)

BASE = 4

args = if !ARGV.empty?

        ARGV
      else
        [Time.new.year]
      end

args.each do |arg|

 ce_year = Integer(arg)
 print "#{ce_year}: " if ARGV.length > 1
 cycle_year     = ce_year - BASE
 stem_number    = cycle_year % 10
 stem_han       = celestial[stem_number]
 stem_pinyin    = pinyin[stem_han]
 element_number = stem_number / 2
 element        = elements[element_number]
 branch_number  = cycle_year % 12
 branch_han     = terrestrial[branch_number]
 branch_pinyin  = pinyin[branch_han]
 animal         = animals[branch_number]
 aspect_number = cycle_year % 2
 aspect        = aspects[aspect_number]
 index         = cycle_year % 60 + 1
 print stem_han, branch_han
 puts " (#{stem_pinyin}-#{branch_pinyin}, #{element} #{animal}; #{aspect} - year #{index} of the cycle)"

end</lang>

Output:

Given arguments 1935 1938 1968 1972 1976:

1935: 乙亥 (yĭ-hài, Wood Pig; yin - year 12 of the cycle)
1938: 戊寅 (wù-yín, Earth Tiger; yang - year 15 of the cycle)
1968: 戊申 (wù-shēn, Earth Monkey; yang - year 45 of the cycle)
1972: 壬子 (rén-zĭ, Water Rat; yang - year 49 of the cycle)
1976: 丙辰 (bĭng-chén, Fire Dragon; yang - year 53 of the cycle)

Given no arguments and run during the year 2017:

丁酉 (dīng-yŏu, Fire Rooster; yin - year 34 of the cycle)

zkl

Translation of: Ruby

<lang zkl>fcn ceToChineseZodiac(ce_year){ // --> list of strings

  # encoding: utf-8
  var [const] pinyin=SD(  // create some static variables, SD is small fixed dictionary
    "甲","jiă",  "乙","yĭ",  "丙","bĭng", "丁","dīng", "戊","wù", "己","jĭ",
    "庚","gēng", "辛","xīn", "壬","rén",  "癸","gŭi",
    "子","zĭ",  "丑","chŏu", "寅","yín", "卯","măo",  "辰","chén", "巳","sì",
    "午","wŭ",  "未","wèi",  "申","shén","酉","yŏu",  "戌","xū",   "亥","hài"
  ),
  celestial  =T("甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"),
  terrestrial=T("子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"),
  animals    =T("Rat",   "Ox",   "Tiger",  "Rabbit",  "Dragon", "Snake",

"Horse", "Goat", "Monkey", "Rooster", "Dog", "Pig"),

  elements   =T("Wood", "Fire", "Earth", "Metal", "Water"),
  aspects    =T("yang","yin"),
  ;
  const BASE=4;

  cycle_year:=ce_year - BASE;
  cycle_year,aspect    := ce_year - BASE,         aspects[cycle_year%2];
  stem_number,element  := cycle_year%10,          elements[stem_number/2];
  stem_han,stem_pinyin := celestial[stem_number], pinyin[stem_han];
  branch_number,animal     := cycle_year%12,      animals[branch_number];
  branch_han,branch_pinyin := terrestrial[branch_number], pinyin[branch_han];
  return(stem_han,branch_han,

stem_pinyin,branch_pinyin, element,animal,aspect) }</lang> <lang zkl>foreach ce_year in (T(1935,1938,1968,1972,1976,Time.Clock.UTC[0])){

  println("%d: %s%s (%s-%s, %s %s; %s)"
          .fmt(ce_year,ceToChineseZodiac(ce_year).xplode()));

}</lang>

Output:
1935: 乙亥 (yĭ-hài, Wood Pig; yin)
1938: 戊寅 (wù-yín, Earth Tiger; yang)
1968: 戊申 (wù-shén, Earth Monkey; yang)
1972: 壬子 (rén-zĭ, Water Rat; yang)
1976: 丙辰 (bĭng-chén, Fire Dragon; yang)
2017: 丁酉 (dīng-yŏu, Fire Rooster; yin)