Plotly Timeline not updating according to selected option of dropdown - drop-down-menu

I have been trying to update the timeline as per the selected item from dropdown but it is not getting plotted as per the selected option.
For example, in attached image i have selected B1 but C1 is extra here. I have tried printing x list too, for B1 it gives [False True False False False False False False False False]. Only 1 true at 2nd location, I am not sure where this C1 is coming from. Results get worst when I chose the options below B1.
The current result:
With the following dataframe used:
Dataframe used
def multi_plot2(df, addAll = True):
grp=df['Group1'].unique()
button_all = dict(label = 'All',
method = 'update',
args = [{'visible': df.columns.isin(df.columns),
'title': 'All',
'showlegend':True}])
def create_layout_button(column):
labels=np.array(df['Label'])
x=np.zeros(labels.size)
i=0
for s in labels:
if column in s:
print (s)
x[i]=1
i=i+1
x=x.astype(np.bool)
print(x)
return dict(label = column,
method = 'restyle',
args = [{'visible': x,
'showlegend': True}])
fig2.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = ([button_all] * addAll) + list(df['Combined'].map(lambda column: create_layout_button(column)))
)
])
fig2.show()
An sample of the dataframe used (that can be copy and pasted)
{'ID': {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10},
'Company': {0: 'Joes',
1: 'Mary',
2: 'Georgia',
3: 'France',
4: 'Butter',
5: 'Player',
6: 'Fish',
7: 'Cattle',
8: 'Swim',
9: 'Seabass'},
'Label': {0: 'Product_A-1',
1: 'Product_B-1',
2: 'Product_C-1',
3: 'Product_A-2',
4: 'Product_A-2',
5: 'Product_B-2',
6: 'Product_C-3',
7: 'Product_D-3',
8: 'Product_A-3',
9: 'Product_D-3'},
'Start': {0: '2021-10-31',
1: '2021-05-31',
2: '2021-10-01',
3: '2021-08-21',
4: '2021-10-01',
5: '2021-08-21',
6: '2021-04-18',
7: '2021-10-31',
8: '2021-08-30',
9: '2021-03-31'},
'End': {0: '2022-10-31',
1: '2022-05-31',
2: '2022-10-01',
3: '2022-08-21',
4: '2022-10-01',
5: '2022-08-21',
6: '2022-04-18',
7: '2022-10-31',
8: '2022-08-30',
9: '2022-03-31'},
'Group1': {0: 'A',
1: 'B',
2: 'C',
3: 'A',
4: 'A',
5: 'B',
6: 'C',
7: 'D',
8: 'A',
9: 'D'},
'Group2': {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3, 9: 3},
'Color': {0: 'Blue',
1: 'Red',
2: 'Green',
3: 'Yellow',
4: 'Green',
5: 'Yellow',
6: 'Red',
7: 'Green',
8: 'Green',
9: 'Yellow'},
'Review': {0: 'Excellent',
1: 'Good',
2: 'Bad',
3: 'Fair',
4: 'Good',
5: 'Bad',
6: 'Fair',
7: 'Excellent',
8: 'Good',
9: 'Bad'},
'url': {0: 'https://www.10xgenomics.com/',
1: 'http://www.3d-medicines.com',
2: 'https://www.89bio.com/',
3: 'https://www.acimmune.com/',
4: 'https://www.acastipharma.com',
5: 'https://acceleratediagnostics.com',
6: 'http://acceleronpharma.com/',
7: 'https://www.acell.com/',
8: 'https://www.acelrx.com',
9: 'https://achievelifesciences.com/'},
'Combined': {0: 'A-1',
1: 'B-1',
2: 'C-1',
3: 'A-2',
4: 'A-2',
5: 'B-2',
6: 'C-3',
7: 'D-3',
8: 'A-3',
9: 'D-3'}}

As far as I can tell, px.timeline produces a figure with one trace. This means that when you pass a boolean array to the visible argument for your buttons, clicking on these buttons these buttons won't correctly update the figure.
Although it's not a very elegant solution, my suggestion would be that you recreate the px.timeline figure using multiple go.Bar traces – one trace for each product label. Then when you pass boolean array to the 'visible' argument as you did in your original code, the buttons should function correctly.
It's not perfect because the horizontal bars don't quite align with the labels (not sure why - any improvements would be greatly appreciated), but here is an example of what you can accomplish:
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
df = pd.DataFrame(
{'ID': {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10},
'Company': {0: 'Joes',
1: 'Mary',
2: 'Georgia',
3: 'France',
4: 'Butter',
5: 'Player',
6: 'Fish',
7: 'Cattle',
8: 'Swim',
9: 'Seabass'},
'Label': {0: 'Product_A-1',
1: 'Product_B-1',
2: 'Product_C-1',
3: 'Product_A-2',
4: 'Product_A-2',
5: 'Product_B-2',
6: 'Product_C-3',
7: 'Product_D-3',
8: 'Product_A-3',
9: 'Product_D-3'},
'Start': {0: '2021-10-31',
1: '2021-05-31',
2: '2021-10-01',
3: '2021-08-21',
4: '2021-10-01',
5: '2021-08-21',
6: '2021-04-18',
7: '2021-10-31',
8: '2021-08-30',
9: '2021-03-31'},
'End': {0: '2022-10-31',
1: '2022-05-31',
2: '2022-10-01',
3: '2022-08-21',
4: '2022-10-01',
5: '2022-08-21',
6: '2022-04-18',
7: '2022-10-31',
8: '2022-08-30',
9: '2022-03-31'},
'Group1': {0: 'A',
1: 'B',
2: 'C',
3: 'A',
4: 'A',
5: 'B',
6: 'C',
7: 'D',
8: 'A',
9: 'D'},
'Group2': {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3, 9: 3},
'Color': {0: 'Blue',
1: 'Red',
2: 'Green',
3: 'Yellow',
4: 'Green',
5: 'Yellow',
6: 'Red',
7: 'Green',
8: 'Green',
9: 'Yellow'},
'Review': {0: 'Excellent',
1: 'Good',
2: 'Bad',
3: 'Fair',
4: 'Good',
5: 'Bad',
6: 'Fair',
7: 'Excellent',
8: 'Good',
9: 'Bad'},
'url': {0: 'https://www.10xgenomics.com/',
1: 'http://www.3d-medicines.com',
2: 'https://www.89bio.com/',
3: 'https://www.acimmune.com/',
4: 'https://www.acastipharma.com',
5: 'https://acceleratediagnostics.com',
6: 'http://acceleronpharma.com/',
7: 'https://www.acell.com/',
8: 'https://www.acelrx.com',
9: 'https://achievelifesciences.com/'},
'Combined': {0: 'A-1',
1: 'B-1',
2: 'C-1',
3: 'A-2',
4: 'A-2',
5: 'B-2',
6: 'C-3',
7: 'D-3',
8: 'B-1',
9: 'D-3'}}
)
# fig2 = px.timeline(
# df, x_start='Start', x_end='End', y='Label'
# )
fig2 = go.Figure()
df['Start'] = pd.to_datetime(df['Start'], format="%Y-%m-%d")
df['End'] = pd.to_datetime(df['End'], format="%Y-%m-%d")
button_all = [
dict(label="All",
method="update",
args = [{'visible': [True]*len(df)}])
]
button_labels = []
for label in df['Combined'].unique():
df_label = df.loc[df['Combined'] == label, ['Start','End','Label']]
# extract milliseconds
df_label['Diff'] = [d.total_seconds()*10**3 for d in (
df_label['End'] - df_label['Start']
)]
fig2.add_trace(go.Bar(
base=df_label['Start'].tolist(),
x=df_label['Diff'].tolist(),
y=df_label['Label'].tolist(),
xcalendar='gregorian',
orientation='h'
))
all_trace_labels = [trace['y'][0] for trace in fig2.data]
for trace in fig2.data:
label = trace['y'][0]
visible_array = [t == label for t in all_trace_labels]
button_labels.append(
dict(label=label,
method="update",
args = [{'visible': visible_array}])
)
fig2.update_layout(
updatemenus=[
dict(
type="dropdown",
direction="down",
buttons=button_all + button_labels,
)
],
xaxis = {
'anchor': 'y',
'automargin': True,
'autorange': True,
'autotypenumbers': 'strict',
'calendar': 'gregorian',
'domain': [0, 1],
'dtick': 'M3',
'tick0': '2000-01-01',
'type': 'date'
},
yaxis = {
'anchor': 'x',
'automargin': True,
'autorange': True,
'autotypenumbers': 'strict',
'categoryorder': 'trace',
'domain': [0, 1],
'dtick': 1,
'side': 'left',
'tick0': 0,
'type': 'category'
}
)
fig2.show()

Related

How to write a function that count each character in a string?

I am trying to write a function that counts the number of times each character appears in a string s. First, I want to use a for loop
for i in range(len(s)):
char = s[i]
Here, I am stuck. How would I proceed from here? Maybe I need to count how many times char appear in the string s.
Then, the output should be...
count_char("practice")
{'p' : 1, 'r' : 1, 'a' : 1, 'c' : 2, 't' : 1, 'i' : 1, 'e' : 1}
Simple Code:
def count_char(s):
result = {}
for i in range(len(s)):
result[s[i]] = s.count(s[i])
return result
print(count_char("practice"))
List comprehension code:
def count_char(s):
return {s[i]:s.count(s[i]) for i in range(len(s))}
print(count_char("practice"))
Result:
{'p': 1, 'r': 1, 'a': 1, 'c': 2, 't': 1, 'i': 1, 'e': 1}

What's the best way i can iterate over a hash in this manner?

so i have this hash and i want to write a program that, given a person’s score can tell them:
a) whether or not they’re allergic to a given item
b) the full list of allergies.
allergies = {
1 => "eggs",
2 => "peanuts",
4 => "shellfish",
8 => "strawberries",
16 => "tomatoes",
32 => "chocolate",
64 => "pollen",
128 => "cat",
}
I thought about how i would achieve this. see the comments below.
# 1+2 = eggs & peanuts (3) # 1+2+4 = eggs & peanuts & shellfish (7) # 1+2+4+8 = eggs & peanuts & shellfish & strawberries (15)
# 1+4 = eggs & shellfish (5) # 1+2+8 = eggs & peanuts & strawberries (11) # 1+2+4+16 = eggs & peanuts & shellfish & tomatoes (23)
# 1+8 = eggs & strawberries (9) # 1+2+16 = eggs & peanuts & tomatoes (19) # 1+2+4+32 = eggs & peanuts & shellfish & chocolate (39)
# 1+16 = eggs & tomatoes (17) # 1+2+32 = eggs & peanuts & chocolate (35) # 1+2+4+64 = eggs & peanuts & shellfish & pollen (71)
# 1+32 = eggs & chocolate (33) # 1+2+64 = eggs & peanuts & pollen (67) # 1+2+4+128 = eggs & peanuts & shellfish & cat (135)
# 1+64 = eggs & pollen (65) # 1+2+128 = eggs & peanuts & car (131) #etc
# 1+128 = eggs & cat (129)
so what i could use help with is, how can i iterate over the hash, making sure all 255 possible combinations be accounted for and return the key associated with that possible combination, so that when i call the method with 35 parameter, it will return the values ["eggs", "peanuts", "chocolate"].
hope that makes sense.
I'm open to other ideas you may have to achieve this.
Thanks!
combinations = 1.upto(allergies.keys.length).map do |i|
allergies.values.combination(i).to_a
end.flatten(1)
# => 255 combinations
lookup_hash = combinations.each_with_object({}) do |combo, memo|
sum = combo.map do |allergy|
allergies.key(allergy)
end.sum
memo[sum] = combo
end
lookup_hash[35]
# => ["eggs", "peanuts", "chocolate"]
To start off, we get all the combinations using the handy Array#combination method.
Next, we turn that array-of-arrays into a hash, which maps the sum (e.g. 35) to the specific combination of ingredients. We make use of Hash#key to find the key in the ingredients hash (a number) corresponding to a specific value (a string).
Now that we have that hash, we can lookup any of the numbers.
Since you are effectively dealing with a bitset here, you can use bitwise logic. Specifically, you can use the bitwise AND operator with a suitable mask to check whether a certain bit is set in your input.
In your case, you can use your allergy keys as a bitmask directly. The numbers you have chosen there have in common that they can be stored in a single byte and each of them have a single bit set at a unique position when represented in binary.
This allows you to check whether this bit is set in the input by using the bitwise AND operator (which is available in Ruby as the & operator on Integers):
35 & 32
# => 32
35 & 16
# => 0
This can be used in a pretty simple method using Enumerable#filter_map available since Ruby 2.7:
ALLERGIES = {
1 => "eggs",
2 => "peanuts",
4 => "shellfish",
8 => "strawberries",
16 => "tomatoes",
32 => "chocolate",
64 => "pollen",
128 => "cat",
}
def get_allergies(allergy_number)
ALLERGIES.filter_map { |key, value| value if allergy_number & key > 0 }
end
get_allergies(35)
# => ["eggs", "peanuts", "chocolate"]
You can use similar logic to check whether a given allergy_number indicates a specific named allergy:
def allergic?(allergy_number, allergy)
key = ALLERGIES.key(allergy)
# return false for unknown allergies
return false unless key
allergy_number & key > 0
end
allergic(35, "chocolate")
# => true
allergic(35, "shellfish")
# => false
Similarly, you can use a bitwise OR operation to generate the allergy_number based on a given list of allergies (which is available in Ruby as the | operator on Integers):
def allergy_number(allergies)
allergies.inject(0) do |result, allergy|
result | (ALLERGIES.key(allergy) || 0)
end
end
allergy_number ["eggs", "peanuts", "chocolate"]
# => 35
allergies = {
1 => "eggs",
2 => "peanuts",
4 => "shellfish",
8 => "strawberries",
16 => "tomatoes"
}
def nbr_to_allergies(allergies, n)
allergies.values_at(*allergies.keys.select { |k| n & k == k })
end
puts "nbr allergies"
puts "-" * 64
(0..2**(allergies.size)-1).each do |n|
puts "#{n.to_s.rjust(2)}: #{nbr_to_allergies(allergies, n)}"
end
displays the following.
nbr allergies
----------------------------------------------------------------
0: []
1: ["eggs"]
2: ["peanuts"]
3: ["eggs", "peanuts"]
4: ["shellfish"]
5: ["eggs", "shellfish"]
6: ["peanuts", "shellfish"]
7: ["eggs", "peanuts", "shellfish"]
8: ["strawberries"]
9: ["eggs", "strawberries"]
10: ["peanuts", "strawberries"]
11: ["eggs", "peanuts", "strawberries"]
12: ["shellfish", "strawberries"]
13: ["eggs", "shellfish", "strawberries"]
14: ["peanuts", "shellfish", "strawberries"]
15: ["eggs", "peanuts", "shellfish", "strawberries"]
16: ["tomatoes"]
17: ["eggs", "tomatoes"]
18: ["peanuts", "tomatoes"]
19: ["eggs", "peanuts", "tomatoes"]
20: ["shellfish", "tomatoes"]
21: ["eggs", "shellfish", "tomatoes"]
22: ["peanuts", "shellfish", "tomatoes"]
23: ["eggs", "peanuts", "shellfish", "tomatoes"]
24: ["strawberries", "tomatoes"]
25: ["eggs", "strawberries", "tomatoes"]
26: ["peanuts", "strawberries", "tomatoes"]
27: ["eggs", "peanuts", "strawberries", "tomatoes"]
28: ["shellfish", "strawberries", "tomatoes"]
29: ["eggs", "shellfish", "strawberries", "tomatoes"]
30: ["peanuts", "shellfish", "strawberries", "tomatoes"]
31: ["eggs", "peanuts", "shellfish", "strawberries", "tomatoes"]
See Hash#values_at and Integer#&.
Assuming that you have stored your allergy pattern in an integer variable ap, i.e. for your example
ap = 35
You can get the allergies for this pattern using
allergie_description = ap
.to_s(2) # convert into bit string
.split(//) # turn into array
.reverse # process right-to-left
.map # get position of "1" bits:
.with_index {|bit, pos| bit == '1' ? pos : nil}
.compact # remove "0" bits
.map {|pos| allergies[1<<pos]} # fetch allergy
.join(', ') # format nicely
This uses the fact that your hash keys basically denote bit positions (.to_s(2) creates a bit-string), counted from up from 1.
You can also use this approach to precompute all possible combinations, as you requested in your question (by looping over all possible integers in the desired range), but if new allergies are added, the number of combinations could become so large that it is better to calculate the combined value on the fly.
Try this approach
allergies = {
1 => "eggs",
2 => "peanuts",
4 => "shellfish",
8 => "strawberries",
16 => "tomatoes",
32 => "chocolate",
64 => "pollen",
128 => "cat",
}
def allergies_by_sum_of_keys(allergies, sum)
allergies_keys = allergies.keys
lookup = {}
(1..allergies_keys.length).each do |i|
allergies_keys.combination(i).to_a.each do |keys|
lookup[keys.sum] = keys.collect{|key| allergies[key] }
end
end
lookup[sum]
end
allergies_by_sum_of_keys(35)
["eggs", "peanuts", "chocolate"]

Algorithm for enumerating all possible ways a rectangle can be split into n smaller rectangles

See title for question. The only other limitation is that the smaller rectangles have to be formed by diving bigger rectangles in half. I have attached the result for n=3 and n=4 below. Hopefully this will suffice to explain the meaning of my questions.
Currently, I have an inefficient recursive algorithm that divides each rectangle horizontally and vertically, and keeps track of all possible combinations in an array. I do not like this algorithm. It is polynomial time, seems unnecessarily complicated and gives me duplicates, as seen in the n=4 picture (hint: look for four equal quadrants)
I was wondering if there might be a better solution to this? I was expermenting with using a 4-ary tree (where each child gets a vertical or horizontal piece), and am able to construct the tree but getting all possible combinations from the tree seems to be eluding me. I'll post my tree boiler plate code below:
class Node:
#value is an tuple (x0,y0,x1,y1)
def __init__(self, value):
self.horizontal = []
self.vertical = []
self.value = value
def createTree(depth, currDepth, node):
if currDepth == depth:
return
node.horizontal.append(Node(getLeftRect(node.value)))
node.horizontal.append(Node(getRightRect(node.value)))
node.vertical.append(Node(getTopRect(node.value)))
node.vertical.append(Node(getBotRect(node.value)))
createTree(depth, currDepth+1, node.horizontal[0])
createTree(depth, currDepth+1, node.horizontal[1])
createTree(depth, currDepth+1, node.vertical[0])
createTree(depth, currDepth+1, node.vertical[1])
Any suggestions/help is welcome!
Note: This is not coursework. I'm trying to make a UI for a custom virtual monitor tool I'm working on.
One strategy is, when we cut vertically, don't let both the left half and the right half have a horizontal cut. This involves some case analysis.
In Python 3, we first have data types to represent subdivided rectangles.
import collections
H = collections.namedtuple('H', ('top', 'bottom')) # horizontal cut
V = collections.namedtuple('V', ('left', 'right')) # vertical cut
W = collections.namedtuple('W', ()) # whole rectangle
Here are the generators.
def generate(n):
assert isinstance(n, int) and n >= 0
yield from generate_with_horizontal(n)
yield from generate_without_horizontal(n)
def generate_with_horizontal(n):
assert isinstance(n, int) and n >= 0
for k in range(n):
for top in generate(k):
for bottom in generate(n - 1 - k):
yield H(top, bottom)
def generate_without_horizontal(n):
assert isinstance(n, int) and n >= 0
if n == 0:
yield W()
for k in range(n):
for left in generate_with_horizontal(k):
for right in generate_without_horizontal(n - 1 - k):
yield V(left, right)
for left in generate_without_horizontal(k):
for right in generate(n - 1 - k):
yield V(left, right)
Idea for a recursive or tree-building algorithm that avoids duplicates:
You start off with a rectangle, and a number of times it has to be divided. Divide it in both directions, and decrease the number by one, and then for each division (vertical and horizontal), partition the number over the two parts.
This method results in 39 divisions when dividing into 4 parts (and 1 duplicate).
The only duplicate I haven't been able to avoid is the cross. Using this method, whenever you have a rectangle that needs to be divided a further 3 or more times, you're going to run into the cross twice. So you'll have to add some additional check for that.
You'll also notice that the 4 groups of 8 solutions resulting from an initial 2,0 partitioning are 90°, 180° and 270° rotations of each other. And the 2 groups of 4 solutions resulting from an initial 1,1 partitioning are 90° rotations of each other. So you can solve only one group, and then rotate to get all the solutions.
It seems the duplicates are going to be harder to avoid with this method than I first thought. If you add 2 more divisions, the seemingly very different L3 R1 and T2 B2 main options lead to several duplicates 4 steps further:
As David Eisenstat mentions in his answer, you can avoid cross doubles by only allowing both halves of a rectangle to be divided in one order (e.g. first vertical, then horizontal) but not the other. This means that when processing a rectangle, you have to be aware where its "other half" is, and whether and how that half has been divided; so that complicates the code needed to use this method.
Here's a recursion in Python that saves the tree as a dictionary where the children are indexed as 2i and 2i + 1. I tried to implement David Eisenstat's suggestion about avoiding a horizontal split on both sides of a vertical split (the number of results seems to correspond with the ones he provided in the comments).
from sets import Set
def f(n):
results = []
def _f(n,result,indexes):
if n == 1:
results.append(result)
return
for i in list(indexes):
indexes.remove(i)
parent = i // 2
sibling = i - 1 if i & 1 else i + 1
left = 2 * i
right = 2 * i + 1
# add horizontal split
if not (False if i < 2 else result[sibling] == 'H' and result[parent] == 'V'):
result_h = result.copy()
indexes_h = indexes.copy()
result_h[i] = 'H'
result_h[left] = result_h[right] = 'W'
indexes_h.add(left)
indexes_h.add(right)
_f(n - 1, result_h, indexes_h)
# add vertical split
result_v = result.copy()
indexes_v = indexes.copy()
result_v[i] = 'V'
result_v[left] = result_v[right] = 'W'
indexes_v.add(left)
indexes_v.add(right)
_f(n - 1, result_v, indexes_v)
_f(n,{1:1},Set([1]))
return results
Results for f(4):
{1: 'H', 2: 'H', 3: 'H', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'H', 2: 'H', 3: 'V', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'H', 2: 'H', 3: 'W', 4: 'H', 5: 'W', 8: 'W', 9: 'W'}
{1: 'H', 2: 'H', 3: 'W', 4: 'V', 5: 'W', 8: 'W', 9: 'W'}
{1: 'H', 2: 'H', 3: 'W', 4: 'W', 5: 'H', 10: 'W', 11: 'W'}
{1: 'H', 2: 'H', 3: 'W', 4: 'W', 5: 'V', 10: 'W', 11: 'W'}
{1: 'H', 2: 'V', 3: 'H', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'H', 2: 'V', 3: 'V', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'H', 2: 'V', 3: 'W', 4: 'H', 5: 'W', 8: 'W', 9: 'W'}
{1: 'H', 2: 'V', 3: 'W', 4: 'V', 5: 'W', 8: 'W', 9: 'W'}
{1: 'H', 2: 'V', 3: 'W', 4: 'W', 5: 'H', 10: 'W', 11: 'W'}
{1: 'H', 2: 'V', 3: 'W', 4: 'W', 5: 'V', 10: 'W', 11: 'W'}
{1: 'H', 2: 'W', 3: 'H', 6: 'H', 7: 'W', 12: 'W', 13: 'W'}
{1: 'H', 2: 'W', 3: 'H', 6: 'V', 7: 'W', 12: 'W', 13: 'W'}
{1: 'H', 2: 'W', 3: 'H', 6: 'W', 7: 'H', 14: 'W', 15: 'W'}
{1: 'H', 2: 'W', 3: 'H', 6: 'W', 7: 'V', 14: 'W', 15: 'W'}
{1: 'H', 2: 'W', 3: 'V', 6: 'H', 7: 'W', 12: 'W', 13: 'W'}
{1: 'H', 2: 'W', 3: 'V', 6: 'V', 7: 'W', 12: 'W', 13: 'W'}
{1: 'H', 2: 'W', 3: 'V', 6: 'W', 7: 'H', 14: 'W', 15: 'W'}
{1: 'H', 2: 'W', 3: 'V', 6: 'W', 7: 'V', 14: 'W', 15: 'W'}
{1: 'V', 2: 'H', 3: 'V', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'V', 2: 'H', 3: 'W', 4: 'H', 5: 'W', 8: 'W', 9: 'W'}
{1: 'V', 2: 'H', 3: 'W', 4: 'V', 5: 'W', 8: 'W', 9: 'W'}
{1: 'V', 2: 'H', 3: 'W', 4: 'W', 5: 'H', 10: 'W', 11: 'W'}
{1: 'V', 2: 'H', 3: 'W', 4: 'W', 5: 'V', 10: 'W', 11: 'W'}
{1: 'V', 2: 'V', 3: 'H', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'V', 2: 'V', 3: 'V', 4: 'W', 5: 'W', 6: 'W', 7: 'W'}
{1: 'V', 2: 'V', 3: 'W', 4: 'H', 5: 'W', 8: 'W', 9: 'W'}
{1: 'V', 2: 'V', 3: 'W', 4: 'V', 5: 'W', 8: 'W', 9: 'W'}
{1: 'V', 2: 'V', 3: 'W', 4: 'W', 5: 'H', 10: 'W', 11: 'W'}
{1: 'V', 2: 'V', 3: 'W', 4: 'W', 5: 'V', 10: 'W', 11: 'W'}
{1: 'V', 2: 'W', 3: 'H', 6: 'H', 7: 'W', 12: 'W', 13: 'W'}
{1: 'V', 2: 'W', 3: 'H', 6: 'V', 7: 'W', 12: 'W', 13: 'W'}
{1: 'V', 2: 'W', 3: 'H', 6: 'W', 7: 'H', 14: 'W', 15: 'W'}
{1: 'V', 2: 'W', 3: 'H', 6: 'W', 7: 'V', 14: 'W', 15: 'W'}
{1: 'V', 2: 'W', 3: 'V', 6: 'H', 7: 'W', 12: 'W', 13: 'W'}
{1: 'V', 2: 'W', 3: 'V', 6: 'V', 7: 'W', 12: 'W', 13: 'W'}
{1: 'V', 2: 'W', 3: 'V', 6: 'W', 7: 'H', 14: 'W', 15: 'W'}
{1: 'V', 2: 'W', 3: 'V', 6: 'W', 7: 'V', 14: 'W', 15: 'W'}
Ok, so it looks like there is a few ways to generate all the non-isomorphic possibilities, so my suggestion would be:
Generate them all for some number of monitors.
Remove duplicates and store results.
When you need them, read from file.
The thing is, unless you need the result for large inputs (say 6-10) you don't want to generate them on-the-fly. Even if you did want to show the results for this size of partition, it would surely be much larger than you could usefully display to the user!
That said, if you do want to generate non-isomorphic representatives of these structures, there is some interesting research on 'sliceable duals' - see for example this masters thesis by Vincent Kusters. Note though that your structures are more general as they include partitions that meet at a four-join.
This is not a direct answer to your question, but you should consider it:
Be very careful while using a recursive algorithm for counting the number of partitions for n>4. The reason is that we can have some partitions in which merging NO two rectangular ends up in another rectangular. For instance, for five partitions, consider the following:
The picture is here
I think the algorithms suggested above miss all partitions similar to this one.

Lodash sorting object by values, without losing the key

Let's say I have an object:
{Derp: 17, Herp: 2, Asd: 5, Foo: 8, Qwe: 12}
And I need to sort it by value. What I'm looking to get is:
{Derp: 17, Qwe: 12, Foo: 8, Asd: 5, Herp: 2}
I'd like to use lodash for it. When I use _.sortBy it doesn't retain the keys how ever:
_.sortBy({Derp: 17, Herp: 2, Asd: 5, Foo: 8, Qwe: 12}).reverse();
// [17, 12, 8, 5, 2]
Hell, I'd even settle for just the array of keys, but still sorted by the value in the input:
['Derp', 'Herp', 'Foo', 'Asd', 'Qwe']
This worked for me
o = _.fromPairs(_.sortBy(_.toPairs(o), 1).reverse())
Here's an example:
var o = {
a: 2,
c: 3,
b: 1
};
o = _.fromPairs(_.sortBy(_.toPairs(o), 1).reverse())
console.log(o);
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
I was struggling with a similar problem and I was able to solve it doing some transforms with lodash. For your problem it would be:
let doo = {Derp: 17, Herp: 2, Asd: 5, Foo: 8, Qwe: 12};
let foo = _.chain(doo)
.map((val, key) => {
return { name: key, count: val }
})
.sortBy('count')
.reverse()
.keyBy('name')
.mapValues('count')
.value();
console.log(foo);
// Derp: 17, Qwe: 12, Foo: 8, Asd: 5, Herp: 2 }
You could try like this,
_.mapValues(_.invert(_.invert(obj)),parseInt);
Object {Herp: 2, Asd: 5, Foo: 8, Qwe: 12, Derp: 17}
or
var obj = {Derp: 17, Herp: 2, Asd: 5, Foo: 8, Qwe: 12}
var result = _.reduceRight(_.invert(_.invert(obj)), function(current, val, key){
current[key] = parseInt(val);
return current;
},{});
Object {Derp: 17, Qwe: 12, Foo: 8, Asd: 5, Herp: 2}
or
Using Chain methods:
_.chain(obj).invert().invert().reduceRight(function(current, val, key){
current[key] = parseInt(val);
return current;
},{}).value()
Object {Derp: 17, Qwe: 12, Foo: 8, Asd: 5, Herp: 2}
Note: It depends on browser usally object properties order is not gurrantee in most case.
Vanilla ES6 version
const obj = {
a: 2,
c: 3,
b: 1
}
const sorted = Object.entries(obj)
.sort((a, b) => a[1] <= b[1] ? -1 : 1)
.reduce((acc, pair) => {
acc[pair[0]] = pair[1]
return acc
}, {})
// Output: { b: 1, a: 2, c: 3 }
This'll also let you access the sort callback, allowing for more granular sorting on deeper object properties.
Simple Vanila javascript approch
Object.fromEntries(Object.entries(obj).sort((a, b) => a[1] - b[1]));

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