Array deducting 1 from previous element when repeated more than once - ruby

I have the following array:
Input:
array = [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
Ouput:
array = [211, 200, 199, 198, 197, 196 ... ]
I've tried each_with_index but couldn't get the desired result.

I don't understand what are to be done with nils, so I haven't addressed that. Let arr be array or array.sort.reverse, depending on requirements. I think this is want you want to do? (See my comment on the question.)
def change_em(arr)
dup_indices = arr.each_index
.group_by { |i| arr[i] }
.values
.flat_map { |a| a.drop(1) }
puts "dup_indices = #{dup_indices}"
last = 0 # anything '-' responds to
arr.each_index.map { |i| last = dup_indices.include?(i) ? last-1 : arr[i] }
end
I've included the puts just to clarify what I'm doing here.
change_em [10, 8, 5, 5, 7]
#=> dup_indices = [3]
#=> [10, 8, 5, 4, 7]
change_em [10, 8, 7, 5, 5]
#=> dup_indices = [4]
#=> [10, 8, 7, 5, 4]
change_em [10, 9, 9, 8, 8, 8]
#=> dup_indices = [2, 4, 5]
#=> [10, 9, 8, 8, 7, 6]
change_em [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 196]
#=> dup_indices = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 196]
Notice that the statement
last = dup_indices.include?(i) ? last-1 : arr[i]
is doing double-duty: it updates the value of last and returns the mapped value for the index i. Note also that dup_indices cannot contain 0.

Not sure I fully understand your requirements, but here's my attempt:
# Transforms an array of numbers into a sorted array of the same length, where
# each successive element is always smaller than the preceding element.
def force_descending(array)
array.sort.reverse.each_with_object([]) do |element, collection|
collection << if collection.empty? || element < collection.last
element
else
collection.last-1
end
end
end
Sample inputs/outputs:
force_descending [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190]
force_descending [10, 8, 5, 5, 7]
#=> [10, 8, 7, 5, 4]
force_descending [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 196]
#=> [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189]

Wrote this is a more functional style.
def f(arr, dup_element = nil, dup_count = 0)
return generate_dup_array(dup_element, dup_count) if arr.empty?
if arr.head != arr.tail.head # Not duplicates
if dup_count == 0 # No duplicates to insert
[arr.head] + f(arr.tail)
else # There are duplicates to insert
generate_dup_array(dup_element, dup_count) + f(arr.tail)
end
else # Duplicate found, continue with tail of array and increase dup_count
f(arr.tail, arr.head, dup_count + 1)
end
end
def generate_dup_array(dup_element, dup_count)
return [] if dup_count == 0
(dup_element - dup_count..dup_element).to_a.reverse
end
class Array
def head; self.first; end
def tail; self[1..-1]; end
end
p f [211, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]
# => [211, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190]
p f [10, 8, 5, 5, 7].sort.reverse
# => [10, 8, 7, 5, 4]
p f [9, 6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 1]
# => [9, 6, 5, 5, 4, 4, 3, 3, 2, 1, 2, 1, 1]

its already in decsending order
For the one-liner crowd:
results = numbers.chunk {|num| num}.flat_map {|num, group| (group.length == 1) ? num : ((num - (group.length-1))..num).to_a.reverse}
For sane programmers:
numbers = [211, 200, 200, 200]
start_of_dups = "_START_" #Something not in the array
dup_count = 0
results = numbers.map do |num|
if start_of_dups == num
dup_count += 1
num - dup_count
else
dup_count = 0
start_of_dups = num
end
end
p results
--output:--
[211, 200, 199, 198]
But if:
array = [10, 10, 10, 9]
--output:--
[10, 9, 8, 9]

Related

Need a Ruby method to convert a binary array to ASCII alpha-numeric string

I have an array of [1, 0, 11, 0, 4, 0, 106, 211, 169, 1, 0, 12, 0, 8, 0, 1, 26, 25, 32, 189, 77, 216, 1, 0, 1, 0, 4, 0, 0, 0, 0, 12, 15].
I would love to create a string version mostly for logging purposes. My end result would be "01000B0004006AD3..."
I could not find a simple way to take each array byte value and pack a string with an ASCII presentation of the byte value.
My solution is cumbersome. I appreciate advice on making the solution slick.
array.each {|x|
value = (x>>4)&0x0f
if( value>9 ) then
result_string.concat (value-0x0a + 'A'.ord).chr
else
result_string.concat (value + '0'.ord).chr
end
value = (x)&0x0f
if( value>9 ) then
result_string.concat (value-0x0a + 'A'.ord).chr
else
result_string.concat (value + '0'.ord).chr
end
}
Your question isn't very clear, but I guess something like this is what you are looking for:
array.map {|n| n.to_s(16).rjust(2, '0').upcase }.join
#=> "01000B0004006AD3A901000C000800011A1920BD4DD80100010004000000000C0F"
or
array.map(&'%02X'.method(:%)).join
#=> "01000B0004006AD3A901000C000800011A1920BD4DD80100010004000000000C0F"
Which one of the two is more readable depends on how familiar your readers are with sprintf-style format strings, I guess.
It's actually pretty simple:
def hexpack(data)
data.pack('C*').unpack('H*')[0]
end
That packs your bytes using integer values (C) and unpacks the resulting string to hex (H). In practice:
hexpack([1, 0, 11, 0, 4, 0, 106, 211, 169, 1, 0, 12, 0, 8, 0, 1, 26, 25, 32, 189, 77, 216, 1, 0, 1, 0, 4, 0, 0, 0, 0, 12, 15])
# => "01000b0004006ad3a901000c000800011a1920bd4dd80100010004000000000c0f"
I might suggest you stick to hex or base64 instead of making your own formatting
dat = [1, 0, 11, 0, 4, 0, 106, 211, 169, 1, 0, 12, 0, 8, 0, 1, 26, 25, 32, 189, 77, 216, 1, 0, 1, 0, 4, 0, 0, 0, 0, 12, 15]
Hexadecimal
hex = dat.map { |x| sprintf('%02x', x) }.join
# => 01000b0004006ad3a901000c000800011a1920bd4dd80100010004000000000c0f
Base64
require 'base64'
base64 = Base64.encode64(dat.pack('c*'))
# => AQALAAQAatOpAQAMAAgAARoZIL1N2AEAAQAEAAAAAAwP\n
Proquints
What? Proquints are pronounceable unique identifiers which makes them great for reading/communicating binary data. In your case, maybe not the best because you're dealing with 30+ bytes here, but they're very suitable for smaller byte strings
# proquint.rb
# adapted to ruby from https://github.com/deoxxa/proquint
module Proquint
C = %w(b d f g h j k l m n p r s t v z)
V = %w(a i o u)
def self.encode (bytes)
bytes << 0 if bytes.size & 1 == 1
bytes.pack('c*').unpack('S*').reduce([]) do |acc, n|
c1 = n & 0x0f
v1 = (n >> 4) & 0x03
c2 = (n >> 6) & 0x0f
v2 = (n >> 10) & 0x03
c3 = (n >> 12) & 0x0f
acc << C[c1] + V[v1] + C[c2] + V[v2] + C[c3]
end.join('-')
end
def decode str
# learner's exercise
# or see some proquint library (eg) https://github.com/deoxxa/proquint
end
end
Proquint.encode dat
# => dabab-rabab-habab-potat-nokab-babub-babob-bahab-pihod-bohur-tadot-dabab-dabab-habab-babab-babub-zabab
Of course the entire process is reversible too. You might not need it, so I'll leave it as an exercise for the learner
It's particularly nice for things like IP address, or any other short binary blobs. You gain familiarity too as you see common byte strings in their proquint form
Proquint.encode [192, 168, 11, 51] # bagop-rasag
Proquint.encode [192, 168, 11, 52] # bagop-rabig
Proquint.encode [192, 168, 11, 66] # bagop-ramah
Proquint.encode [192, 168, 22, 19] # bagop-kisad
Proquint.encode [192, 168, 22, 20] # bagop-kibid

Ruby 100 doors returning 100 nil

I'm solving the '100 doors' problem from Rosetta Code in Ruby. Briefly,
there are 100 doors, all closed, designated 1 to 100
100 passes are made, designated 1 to 100
on the ith pass, every ith door is "toggled": opened if it's closed, closed if it's open
determine the state of each door after 100 passes have been completed.
Therefore, on the first pass all doors are opened. On the second pass even numbered doors are closed. On the third pass doors i for which i%3 == 0 are toggled, and so on.
Here is my attempt at solving the problem.
visit_number = 0
door_array = []
door_array = 100.times.map {"closed"}
until visit_number == 100 do
door_array = door_array.map.with_index { |door_status, door_index|
if (door_index + 1) % (visit_number + 1) == 0
if door_status == "closed"
door_status = "open"
elsif door_status == "open"
door_status = "closed"
end
end
}
visit_number += 1
end
print door_array
But it keeps printing me an array of 100 nil when I run it: Look at all this nil !
What am I doing wrong?
That's what your if clauses return. Just add a return value explicitly.
until visit_number == 100 do
door_array = door_array.map.with_index { |door_status, door_index|
if (door_index + 1) % (visit_number + 1) == 0
if door_status == "closed"
door_status = "open"
elsif door_status == "open"
door_status = "closed"
end
end
door_status
}
visit_number += 1
end
OR:
1.upto(10) {|i| door_array[i*i-1] = 'open'}
The problem is the outer if block doesn't explicitly return anything (thus returns nil implicitly) when the condition does not meet.
A quick fix:
visit_number = 0
door_array = []
door_array = 100.times.map {"closed"}
until visit_number == 100 do
door_array = door_array.map.with_index { |door_status, door_index|
if (door_index + 1) % (visit_number + 1) == 0
if door_status == "closed"
door_status = "open"
elsif door_status == "open"
door_status = "closed"
end
else #<------------- Here
door_status #<------------- And here
end
}
visit_number += 1
end
print door_array
Consider these approaches:
door_array.map { |door|
case door
when "open"
"closed"
when "closed"
"open"
end
}
or
rule = { "open" => "closed", "closed" => "open" }
door_array.map { |door| rule[door] }
or
door_array.map { |door| door == 'open' ? 'closed' : 'open' }
Code
require 'prime'
def even_nbr_divisors?(n)
return false if n==1
arr = Prime.prime_division(n).map { |v,exp| (0..exp).map { |i| v**i } }
arr.shift.product(*arr).map { |a| a.reduce(:*) }.size.even?
end
closed, open = (1..100).partition { |n| even_nbr_divisors?(n) }
closed #=> [ 2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22,
# 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40,
# 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 56, 57,
# 58, 59, 60, 61, 62, 63, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
# 75, 76, 77, 78, 79, 80, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
# 92, 93, 94, 95, 96, 97, 98, 99],
open #= [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Explanation
All doors are initially closed. Consider the 24th door. It is toggled during the following passes:
pass 1: opened
pass 2: closed
pass 3: opened
pass 4: closed
pass 6: opened
pass 8: closed
pass 12: opened
pass 24: closed
Notice that the door is toggled once for each of 24's divisors. Therefore, if we had a method divisors(n) that returned an array of n's divisors, we could determine the number of toggles as follows:
nbr_toggles = divisors(24).size
#=> [1,2,3,4,6,8,12,24].size
#=> 8
Since the door is toggled an even number of times, we conclude that it will be in its original state (closed) after all the dust has settled. Similarly, for n = 9,
divisors(9).size
#=> [1,3,9].size
#=> 3
We therefore conclude door #9 will be open at the end, since 3 is odd.
divisors can be defined as follows.
def divisors(n)
arr = Prime.prime_division(n).map { |v,exp| (0..exp).map { |i| v**i } }
arr.first.product(*arr[1..-1]).map { |a| a.reduce(:*) }
end
For example,
divisors 24
#=> [1, 3, 2, 6, 4, 12, 8, 24]
divisors 9
#=> [1, 3, 9]
divisors 1800
#=> [1, 5, 25, 3, 15, 75, 9, 45, 225, 2, 10, 50, 6, 30, 150, 18, 90, 450,
# 4, 20, 100, 12, 60, 300, 36, 180, 900, 8, 40, 200, 24, 120, 600, 72,
# 360, 1800]
Since we only care if there are an odd or even number of divisors, we can instead write
def even_nbr_divisors?(n)
return false if n==1
arr = Prime.prime_division(n).map { |v,exp| (0..exp).map { |i| v**i } }
arr.shift.product(*arr).map { |a| a.reduce(:*) }.size.even?
end
For n = 24, the steps are as follows:
n = 24
a = Prime.prime_division(n)
#=> [[2, 3], [3, 1]]
arr = a.map { |v,exp| (0..exp).map { |i| v**i } }
#=> [[1, 2, 4, 8], [1, 3]]
b = arr.shift
#=> [1, 2, 4, 8]
arr
#=> [[1, 3]]
c = b.product(*arr)
#=> [[1, 1], [1, 3], [2, 1], [2, 3], [4, 1], [4, 3], [8, 1], [8, 3]]
d = c.map { |a| a.reduce(:*) }
#=> [1, 3, 2, 6, 4, 12, 8, 24]
e = d.size
#=> 8
e.even?
#=> true
Lastly,
(1..100).partition { |n| even_nbr_divisors?(n) }
returns the result shown above.

How to insert elements into an array

I have an array consisting of unknown elements:
myary = [100, "hello", 20, 40, "hi"]
I want to put integer 10 after each element to make it into this:
myary = [100, 10, "hello", 10, 20, 10, 40, 10, "hi", 10]
Is there a way or a method to do it?
Another problem is that I need to add integer 10 before a string "hello".
myary = [100, 10,"hello", 20, 40, "hi"]
Is this what you want ?
myary = [100, "hello", 20, 40, "hi"]
myary.flat_map { |i| [i, 10] }
# => [100, 10, "hello", 10, 20, 10, 40, 10, "hi", 10]
myary.flat_map { |i| i == 'hello' ? [10, i] : i }
# => [100, 10,"hello", 20, 40, "hi"]
Read #flat_map method.

How do I make my class objects return an array instead of <Classobject:0x007fc94a0...]>?

!!!! I'm cleaning up my code and rethinking my question. I'll repost and edited version in a few minutes. Thanks for the responses!
Here's my code:
class Student
attr_accessor :scores, :first_name
def initialize(first_name, scores)
#first_name = first_name
#scores = scores
end
def average
#scores.inject {|sum, el| sum + el }.to_f / #scores.size
end
def letter_grade
case average
when (90..100)
"A"
when (80..89)
"B"
when (70..79)
"C"
when (60..69)
"D"
when (0..59)
"F"
end
end
end
me = Student.new("Alex", [100,100,100,0,100])
student_2 = Student.new('Hodor', [2,2,7,0,90])
student_3 = Student.new('Aria', [90,100,87,90,90])
student_4 = Student.new('Tyrion', [95,100,97,100,30])
student_5 = Student.new('Jaela', [100,100,100,100,100])
students = [me, student_2, student_3, student_4, student_5]
p students
Here's what I get back:
[#<Student:0x007f92a91e6070 #first_name="Alex", #scores=[100, 100, 100, 0, 100]>, #<Student:0x007f92a91e5ff8 #first_name="Hodor", #scores=[2, 2, 7, 0, 90]>, #<Student:0x007f92a91e5f80 #first_name="Aria", #scores=[90, 100, 87, 90, 90]>, #<Student:0x007f92a91e5f08 #first_name="Tyrion", #scores=[95, 100, 97, 100, 30]>, #<Student:0x007f92a91e5e90 #first_name="Jaela", #scores=[100, 100, 100, 100, 100]>]
I want something like [["Alex", [100, 100, 100, 0, 100], ["Hodor", [2..]..]]
The goal is to have these tests pass:
p linear_search(students, "Alex") == 0
p linear_search(students, "NOT A STUDENT") == -1
So I actually need this to happen within the Student class, I think.
I'm not sure what's the purpose of the exercise, but to get from your actual output to your expected output, you just have to go over your elements, and build an array out of each one (use map):
students.map { |x| [x.first_name, x.scores] }
# => [["Alex", [100, 100, 100, 0, 100]], ["Hodor", [2, 2, 7, 0, 90]], ["Aria", [90, 100, 87, 90, 90]], ["Tyrion", [95, 100, 97, 100, 30]], ["Jaela", [100, 100, 100, 100, 100]]]
If you try to output an instance of Student, ruby calls to_s() on the Student instance. If your class does not provide a to_s() method, then the inherited to_s() method(in class Object) is called, which provides the string you see. If you redefine Object#to_s, you can prove that:
#Your code here
class Object
def to_s
'hello from Object#to_s'
end
end
p students
--output:--
[hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s,
hello from Object#to_s]
If you override the to_s() method inside Student, then ruby will call it and use its return value whenever you try to output a Student object:
require 'pp'
class Student
attr_accessor :scores, :first_name
...
def to_s
"#{first_name} #{scores.inspect}"
end
end
students = [
Student.new("Alex", [100,100,100,0,100]),
Student.new('Hodor', [2,2,7,0,90]),
Student.new('Aria', [90,100,87,90,90]),
Student.new('Tyrion', [95,100,97,100,30]),
Student.new('Jaela', [100,100,100,100,100]),
]
pp students
--output:--
[Alex [100, 100, 100, 0, 100],
Hodor [2, 2, 7, 0, 90],
Aria [90, 100, 87, 90, 90],
Tyrion [95, 100, 97, 100, 30],
Jaela [100, 100, 100, 100, 100]]
In the code fragment scores.inspect the inspect() method is what p uses, i.e. p scores is equivalent to print scores.inspect + "\n". But you can't write:
"some string #{p.scores}"
because string interpolation uses the return value of p.scores, and p, like puts, always returns nil.

Is it possible to calculate the Chinese Zodiac, or must I use a lookup table?

I'm building an application that will tell your Chinese sign. I looked around but only found charts (from 1900 to 2020), and no logic to create something more dynamic.
Is there no logic for determining a Chinese zodiac?
Does that answer your question:
public string ChineseZodiac(System.DateTime date)
{
System.Globalization.EastAsianLunisolarCalendar cc =
new System.Globalization.ChineseLunisolarCalendar();
int sexagenaryYear = cc.GetSexagenaryYear(date);
int terrestrialBranch = cc.GetTerrestrialBranch(sexagenaryYear);
// string[] years = "rat,ox,tiger,hare,dragon,snake,horse,sheep,monkey,fowl,dog,pig".Split(',');
// string[] years = "Rat,Ox,Tiger,Rabbit,Dragon,Snake,Horse,Goat,Monkey,Rooster,Dog,Pig".Split(',');
// string[] years = new string[]{ "rat", "ox", "tiger", "hare", "dragon", "snake", "horse", "sheep", "monkey", "fowl", "dog", "pig" };
string[] years = new string[]{ "Rat", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Goat", "Monkey", "Rooster", "Dog", "Pig" };
return years[terrestrialBranch - 1];
} // End Function ChineseZodiac
Fore those that need the source code for another programming language:
I've ripped the corresponding sources out of .NET Framwork, here:
https://gist.github.com/ststeiger/709354299a457e2d79b06d0127096fee
Edit:
I ported this to JavaScript:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChineseZodiac = void 0;
var DateTimeKind;
(function (DateTimeKind) {
DateTimeKind[DateTimeKind["Unspecified"] = 0] = "Unspecified";
DateTimeKind[DateTimeKind["Utc"] = 1] = "Utc";
DateTimeKind[DateTimeKind["Local"] = 2] = "Local";
})(DateTimeKind || (DateTimeKind = {}));
var GregorianCalendar = (function () {
function GregorianCalendar() {
}
GregorianCalendar.prototype.GetYear = function (time) {
var ticks = time.JavaScriptTicks;
var dt = new Date(ticks);
return dt.getUTCFullYear();
};
GregorianCalendar.prototype.GetMonth = function (time) {
var ticks = time.JavaScriptTicks;
var dt = new Date(ticks);
return (dt.getUTCMonth() + 1);
};
GregorianCalendar.prototype.GetDayOfMonth = function (time) {
var ticks = time.JavaScriptTicks;
var dt = new Date(ticks);
return dt.getUTCDate();
};
return GregorianCalendar;
}());
var DotNetDateTime = (function () {
function DotNetDateTime(ticks, dt) {
this.m_ticks = 0;
this.m_ticks = ticks;
}
DotNetDateTime.fromJsDate = function (dt) {
var jsTicks = dt.getTime();
var dotNetJsbaseTicks = 621355968000000000;
var tenK = 10000;
var dotTicks = dotNetJsbaseTicks + jsTicks * tenK;
return new DotNetDateTime(dotTicks, DateTimeKind.Unspecified);
};
Object.defineProperty(DotNetDateTime.prototype, "Ticks", {
get: function () {
return this.m_ticks;
},
enumerable: false,
configurable: true
});
Object.defineProperty(DotNetDateTime.prototype, "JavaScriptTicks", {
get: function () {
var dotNetJsbaseTicks = 621355968000000000;
var dotNetTicksSince1970 = this.m_ticks - dotNetJsbaseTicks;
var jsTicks = parseInt((dotNetTicksSince1970 / 10000).toString(), 10);
return jsTicks;
},
enumerable: false,
configurable: true
});
return DotNetDateTime;
}());
var MinLunisolarYear = 1901;
var MaxLunisolarYear = 2100;
var DaysPerYear = 365;
var DaysPer4Years = DaysPerYear * 4 + 1;
var DaysPer100Years = DaysPer4Years * 25 - 1;
var DaysPer400Years = DaysPer100Years * 4 + 1;
var DaysTo10000 = DaysPer400Years * 25 - 366;
var TicksPerMillisecond = 10000;
var TicksPerSecond = TicksPerMillisecond * 1000;
var TicksPerMinute = TicksPerSecond * 60;
var TicksPerHour = TicksPerMinute * 60;
var TicksPerDay = TicksPerHour * 24;
var MinTicks = 0;
var MaxTicks = DaysTo10000 * TicksPerDay - 1;
var MinValue = new DotNetDateTime(MinTicks, DateTimeKind.Unspecified);
var MaxValue = new DotNetDateTime(MaxTicks, DateTimeKind.Unspecified);
var Jan1Month = 1;
var Jan1Date = 2;
var nDaysPerMonth = 3;
var s_daysToMonth365 = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
var s_daysToMonth366 = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
var s_yinfo = [
[0, 2, 19, 19168],
[0, 2, 8, 42352],
[5, 1, 29, 21096],
[0, 2, 16, 53856],
[0, 2, 4, 55632],
[4, 1, 25, 27304],
[0, 2, 13, 22176],
[0, 2, 2, 39632],
[2, 1, 22, 19176],
[0, 2, 10, 19168],
[6, 1, 30, 42200],
[0, 2, 18, 42192],
[0, 2, 6, 53840],
[5, 1, 26, 54568],
[0, 2, 14, 46400],
[0, 2, 3, 54944],
[2, 1, 23, 38608],
[0, 2, 11, 38320],
[7, 2, 1, 18872],
[0, 2, 20, 18800],
[0, 2, 8, 42160],
[5, 1, 28, 45656],
[0, 2, 16, 27216],
[0, 2, 5, 27968],
[4, 1, 24, 44456],
[0, 2, 13, 11104],
[0, 2, 2, 38256],
[2, 1, 23, 18808],
[0, 2, 10, 18800],
[6, 1, 30, 25776],
[0, 2, 17, 54432],
[0, 2, 6, 59984],
[5, 1, 26, 27976],
[0, 2, 14, 23248],
[0, 2, 4, 11104],
[3, 1, 24, 37744],
[0, 2, 11, 37600],
[7, 1, 31, 51560],
[0, 2, 19, 51536],
[0, 2, 8, 54432],
[6, 1, 27, 55888],
[0, 2, 15, 46416],
[0, 2, 5, 22176],
[4, 1, 25, 43736],
[0, 2, 13, 9680],
[0, 2, 2, 37584],
[2, 1, 22, 51544],
[0, 2, 10, 43344],
[7, 1, 29, 46248],
[0, 2, 17, 27808],
[0, 2, 6, 46416],
[5, 1, 27, 21928],
[0, 2, 14, 19872],
[0, 2, 3, 42416],
[3, 1, 24, 21176],
[0, 2, 12, 21168],
[8, 1, 31, 43344],
[0, 2, 18, 59728],
[0, 2, 8, 27296],
[6, 1, 28, 44368],
[0, 2, 15, 43856],
[0, 2, 5, 19296],
[4, 1, 25, 42352],
[0, 2, 13, 42352],
[0, 2, 2, 21088],
[3, 1, 21, 59696],
[0, 2, 9, 55632],
[7, 1, 30, 23208],
[0, 2, 17, 22176],
[0, 2, 6, 38608],
[5, 1, 27, 19176],
[0, 2, 15, 19152],
[0, 2, 3, 42192],
[4, 1, 23, 53864],
[0, 2, 11, 53840],
[8, 1, 31, 54568],
[0, 2, 18, 46400],
[0, 2, 7, 46752],
[6, 1, 28, 38608],
[0, 2, 16, 38320],
[0, 2, 5, 18864],
[4, 1, 25, 42168],
[0, 2, 13, 42160],
[10, 2, 2, 45656],
[0, 2, 20, 27216],
[0, 2, 9, 27968],
[6, 1, 29, 44448],
[0, 2, 17, 43872],
[0, 2, 6, 38256],
[5, 1, 27, 18808],
[0, 2, 15, 18800],
[0, 2, 4, 25776],
[3, 1, 23, 27216],
[0, 2, 10, 59984],
[8, 1, 31, 27432],
[0, 2, 19, 23232],
[0, 2, 7, 43872],
[5, 1, 28, 37736],
[0, 2, 16, 37600],
[0, 2, 5, 51552],
[4, 1, 24, 54440],
[0, 2, 12, 54432],
[0, 2, 1, 55888],
[2, 1, 22, 23208],
[0, 2, 9, 22176],
[7, 1, 29, 43736],
[0, 2, 18, 9680],
[0, 2, 7, 37584],
[5, 1, 26, 51544],
[0, 2, 14, 43344],
[0, 2, 3, 46240],
[4, 1, 23, 46416],
[0, 2, 10, 44368],
[9, 1, 31, 21928],
[0, 2, 19, 19360],
[0, 2, 8, 42416],
[6, 1, 28, 21176],
[0, 2, 16, 21168],
[0, 2, 5, 43312],
[4, 1, 25, 29864],
[0, 2, 12, 27296],
[0, 2, 1, 44368],
[2, 1, 22, 19880],
[0, 2, 10, 19296],
[6, 1, 29, 42352],
[0, 2, 17, 42208],
[0, 2, 6, 53856],
[5, 1, 26, 59696],
[0, 2, 13, 54576],
[0, 2, 3, 23200],
[3, 1, 23, 27472],
[0, 2, 11, 38608],
[11, 1, 31, 19176],
[0, 2, 19, 19152],
[0, 2, 8, 42192],
[6, 1, 28, 53848],
[0, 2, 15, 53840],
[0, 2, 4, 54560],
[5, 1, 24, 55968],
[0, 2, 12, 46496],
[0, 2, 1, 22224],
[2, 1, 22, 19160],
[0, 2, 10, 18864],
[7, 1, 30, 42168],
[0, 2, 17, 42160],
[0, 2, 6, 43600],
[5, 1, 26, 46376],
[0, 2, 14, 27936],
[0, 2, 2, 44448],
[3, 1, 23, 21936],
[0, 2, 11, 37744],
[8, 2, 1, 18808],
[0, 2, 19, 18800],
[0, 2, 8, 25776],
[6, 1, 28, 27216],
[0, 2, 15, 59984],
[0, 2, 4, 27296],
[4, 1, 24, 43872],
[0, 2, 12, 43744],
[0, 2, 2, 37600],
[3, 1, 21, 51568],
[0, 2, 9, 51552],
[7, 1, 29, 54440],
[0, 2, 17, 54432],
[0, 2, 5, 55888],
[5, 1, 26, 23208],
[0, 2, 14, 22176],
[0, 2, 3, 42704],
[4, 1, 23, 21224],
[0, 2, 11, 21200],
[8, 1, 31, 43352],
[0, 2, 19, 43344],
[0, 2, 7, 46240],
[6, 1, 27, 46416],
[0, 2, 15, 44368],
[0, 2, 5, 21920],
[4, 1, 24, 42448],
[0, 2, 12, 42416],
[0, 2, 2, 21168],
[3, 1, 22, 43320],
[0, 2, 9, 26928],
[7, 1, 29, 29336],
[0, 2, 17, 27296],
[0, 2, 6, 44368],
[5, 1, 26, 19880],
[0, 2, 14, 19296],
[0, 2, 3, 42352],
[4, 1, 24, 21104],
[0, 2, 10, 53600],
[8, 1, 30, 59696],
[0, 2, 18, 54560],
[0, 2, 7, 55968],
[6, 1, 27, 27472],
[0, 2, 15, 22224],
[0, 2, 5, 19168],
[4, 1, 25, 42216],
[0, 2, 12, 41680],
[0, 2, 1, 53584],
[2, 1, 21, 55592],
[0, 2, 9, 54560],
];
function GregorianIsLeapYear(y) {
if ((y % 4) != 0) {
return false;
}
if ((y % 100) != 0) {
return true;
}
return (y % 400) == 0;
}
function GetYearInfo(lunarYear, index) {
if (lunarYear < MinLunisolarYear || lunarYear > MaxLunisolarYear) {
throw new Error("year");
}
return s_yinfo[lunarYear - MinLunisolarYear][index];
}
function CheckTicksRange(ticks) {
if (ticks < MinValue.Ticks || ticks > MaxValue.Ticks) {
throw new Error("time");
}
}
function GregorianToLunar(solarYear, solarMonth, solarDate) {
var outData = { lunarYear: 0, lunarMonth: 0, lunarDate: 0 };
var isLeapYear = GregorianIsLeapYear(solarYear);
var jan1Month;
var jan1Date;
var solarDay = isLeapYear ? s_daysToMonth366[solarMonth - 1] : s_daysToMonth365[solarMonth - 1];
solarDay += solarDate;
var lunarDay = solarDay;
outData.lunarYear = solarYear;
if (outData.lunarYear == (MaxLunisolarYear + 1)) {
outData.lunarYear--;
lunarDay += (GregorianIsLeapYear(outData.lunarYear) ? 366 : 365);
jan1Month = GetYearInfo(outData.lunarYear, Jan1Month);
jan1Date = GetYearInfo(outData.lunarYear, Jan1Date);
}
else {
jan1Month = GetYearInfo(outData.lunarYear, Jan1Month);
jan1Date = GetYearInfo(outData.lunarYear, Jan1Date);
if ((solarMonth < jan1Month) ||
(solarMonth == jan1Month && solarDate < jan1Date)) {
outData.lunarYear--;
lunarDay += (GregorianIsLeapYear(outData.lunarYear) ? 366 : 365);
jan1Month = GetYearInfo(outData.lunarYear, Jan1Month);
jan1Date = GetYearInfo(outData.lunarYear, Jan1Date);
}
}
lunarDay -= s_daysToMonth365[jan1Month - 1];
lunarDay -= (jan1Date - 1);
var mask = 0x8000;
var yearInfo = GetYearInfo(outData.lunarYear, nDaysPerMonth);
var days = ((yearInfo & mask) != 0) ? 30 : 29;
outData.lunarMonth = 1;
while (lunarDay > days) {
lunarDay -= days;
outData.lunarMonth++;
mask >>= 1;
days = ((yearInfo & mask) != 0) ? 30 : 29;
}
outData.lunarDate = lunarDay;
return outData;
}
function TimeToLunar(time) {
var gregorianCalendar = new GregorianCalendar();
var gy = gregorianCalendar.GetYear(time);
var gm = gregorianCalendar.GetMonth(time);
var gd = gregorianCalendar.GetDayOfMonth(time);
var ad = GregorianToLunar(gy, gm, gd);
return {
year: ad.lunarYear
};
}
function GetSexagenaryYear(time) {
CheckTicksRange(time.Ticks);
var x = TimeToLunar(time);
return ((x.year - 4) % 60) + 1;
}
function GetTerrestrialBranch(sexagenaryYear) {
if (sexagenaryYear < 1 || sexagenaryYear > 60) {
throw new Error("sexagenaryYear");
}
return ((sexagenaryYear - 1) % 12) + 1;
}
function ChineseZodiac(date) {
var dotNetDate = DotNetDateTime.fromJsDate(date);
var sexagenaryYear = GetSexagenaryYear(dotNetDate);
var terrestrialBranch = GetTerrestrialBranch(sexagenaryYear);
var years = ["Rat", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Goat", "Monkey", "Rooster", "Dog", "Pig"];
return years[terrestrialBranch - 1];
}
exports.ChineseZodiac = ChineseZodiac;
Testing:
var exports = {};
// [copy-pasting above code]
exports.ChineseZodiac(new Date(1970,0,1))
exports.ChineseZodiac(new Date(2021,0,1))
exports.ChineseZodiac(new Date(2022,0,1))
all returns the same values as dotnet.
By using a calculator
2013-4=2009
2009/12= 167.41666
167*12=2004
2009-2004=5 (5 is snake which was the animal for 2013)
Use this to calculate the year.
<?php
$year = 2013;
switch (($year - 4) % 12) {
case 0: $zodiac = 'Rat'; break;
case 1: $zodiac = 'Ox'; break;
case 2: $zodiac = 'Tiger'; break;
case 3: $zodiac = 'Rabbit'; break;
case 4: $zodiac = 'Dragon'; break;
case 5: $zodiac = 'Snake'; break;
case 6: $zodiac = 'Horse'; break;
case 7: $zodiac = 'Goat'; break;
case 8: $zodiac = 'Monkey'; break;
case 9: $zodiac = 'Rooster'; break;
case 10: $zodiac = 'Dog'; break;
case 11: $zodiac = 'Pig'; break;
}
echo "{$year} is the year of the {$zodiac}.<br />";
?>
Wikipedia has a reference to 2044.
http://en.wikipedia.org/wiki/Chinese_zodiac
Using Year of the Rat as an example (for years after 1984), it looks like Rat cycles every:
383, 353, 353, 383, 354 days
Notice the last cycle is 354 which is more than likely due to Leap Year. Maybe using this formula, you can work out any year up to maybe 2100 or so.
I used the following T-SQL to deduce those numbers
select DATEDIFF(D,'02/2/1984', '02/19/1985')
select DATEDIFF(D,'02/19/1996', '02/6/1997')
select DATEDIFF(D,'02/7/2008', '01/25/2009')
select DATEDIFF(D,'01/25/2020', '02/11/2021')
select DATEDIFF(D,'02/11/2032', '01/30/2033')
If you are serious about finding a non-tabular mechanism for calculating the years of the Chinese Zodiac, then I recommend looking at 'Calendrical Calculations, 3rd Edition' which has (LISP) code to handle calculations for the Chinese New Year, and from that, deducing the Year of the <relevant-animal> is straight-forward. That book covers many calendrical systems and is an interesting read. Being a luni-solar calendar, the Chinese calendar is quite complex; the mathematics gets quite detailed.
It is probably simpler, and likely more compact, code-wise, to use a table, though.
There is always a question what is quicker to test and verify.
When developing chinese zodiac calculator on calculla, we decided to use lookup table - as this was just quicker and more convenient to code, than actually testing any algo for it (even if algo may be simple, you still need time to test it).
This lookup was not a big table and you can actually get the javascript code from source of our website.
First of all you must create an array of the zodiac signs exactly as below
DECLARE sign : [array(1.....12)]
DECLARE year : INTEGER
sign(1) ← "Rat"
sign(2) ← "Ox"
sign(3) ← "Tiger"
sign(4) ← "Rabbit"
sign(5) ← "Dragon"
sign(6) ← "Snake"
sign(7) ← "Horse"
sign(8) ← "Goat"
sign(9) ← "Monkey"
sign(10) ← "Rooster"
sign(11) ← "Dog"
sign(12) ← "Pig"
DECLARE X, Y, N : INTEGER
X ← (year - 4)
Y ← (X DIV 12) // DIV return only the natural number
Y ← (Y * 12)
N ← N + 1
OUTPUT sign(N)
Public Function ChineseSign(ByVal DoB As Date) As String
Dim CSign As String = ""
Dim YearSign As New Short
YearSign = Year(DoB) Mod 12
'// Uncomment the following to use Feb 12th as New Year calculation //
'If Month(DoB) = 1 Then
'YearSign = YearSign - 1
'If YearSign = 0 Then YearSign = 12
'ElseIf Month(DoB) = 2 And DoB.Day < 12 Then
'YearSign = YearSign - 1
'If YearSign = 0 Then YearSign = 12
'End If
Select Case YearSign
Case 1
CSign = "Rooster"
Case 2
CSign = "Dog"
Case 3
CSign = "Pig (Boar)"
Case 4
CSign = "Rat"
Case 5
CSign = "Ox"
Case 6
CSign = "Tiger"
Case 7
CSign = "Rabbit"
Case 8
CSign = "Dragon"
Case 9
CSign = "Snake"
Case 10
CSign = "Horse"
Case 11
CSign = "Goat"
Case 12
CSign = "Monkey"
End Select
Select Case CSign
Case "Ox"
Return "an " & CSign
Case Else
Return "a " & CSign
End Select
End Function

Resources