How to sort a custom column in DT::datatable - datatable

This is in continuation of another post here -
How to create a custom column (sortable) in DT::datatable
The approach presented there appears to be failing for below dataset when it is placed within a shiny app.
I wanted to sort according to Col_9 in the dataset.
Below is my Shiny app
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
DTOutput("aaa", height = '400px')
)
server <- function(input, output) {
output$aaa =
DT::renderDT({
df =
structure(list(Col__1 = c("A", "A", "A", "A", "B", "C", "C",
"D", "G", "G", "H", "H", "J", "K", "K", "M", "M", "M", "M", "M",
"O", "P", "P", "R", "T", "T", "T", "U", "U", "W"), Col__2 = c(1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234), Col__3 = c(1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234), Col__4 = c(NA, 36, NA, 1,
4, 1, NA, 65, NA, 396, 7, 2, 3, 29, 4, 185, 651, NA, 1, NA, 2,
NA, 27, 92, 35, 29, NA, 60, 1, 144), Col__5 = c(NA, 1012, NA,
12, 350, 98, NA, 3925, NA, 4729, 327, 5, 87, 310, 30, 1854, 13013,
NA, 1, NA, 122, NA, 1354, 1629, 3278, 450, NA, 1808, 21, 948),
Col__6 = c(1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234), Col__7 = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
Col__8 = c(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), Col__9 = c("-",
" 36 (2%)", "-", " 1 (2%)", " 4 (1%)", " 1 (1%)", "-",
" 65 (1%)", "-", "396 (6%)", " 7 (1%)", " 2 (4%)", " 3 (2%)",
" 29 (4%)", " 4 (1%)", "185 (6%)", "651 (4%)", "-", " 1 (8%)",
"-", " 2 (1%)", "-", " 27 (2%)", " 92 (3%)", " 35 (1%)",
" 29 (3%)", "-", " 60 (2%)", " 1 (2%)", "144 (10%)"), Col__10 = c(2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5)), row.names = c(NA, -30L), class = "data.frame")
{ ### Few Important Definitions
Render3 = JS(
"function(data, type, row, meta){",
" if(type === 'sort' || type === 'type'){",
" return row[4];",
" } else {",
" return data;",
" }",
"}"
)
}
datatable(df,
callback = JS("$('table.dataTable.no-footer').css('border-bottom', '1px solid #b7b7b7');"),
options = list(pageLength = dim(df)[1], bLengthChange = FALSE, autoWidth = FALSE, bInfo = FALSE, searching = FALSE, info = FALSE, bPaginate = FALSE,
order = list(list(7, 'desc')),
columnDefs =
list(
list(width = '40%', targets = 1, className = 'dt-center dt-center1'),
list(width = '0%', targets = 2, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 3, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 4, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 5, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 6, className = 'dt-center dt-center1', visible = FALSE),
list(width = '15%', targets = 9, className = 'dt-center dt-center1', visible = TRUE, render = Render3),
list(targets = 0, visible = FALSE)
)))
})
}
shinyApp(ui, server)
However surprisingly, this same approach is perfectly working outside of shiny app.
Below is the snapshot of the problem when run within a Shiny-app -
Any pointer towards the right approach will be very helpful.

Related

How can I fix the position of the header of data.table in shiny-app

Let say I have below app -
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
div(style = "height: 300px; width: 100%; overflow-y: scroll;", DTOutput("aaa", height = '400px'))
)
server <- function(input, output) {
output$aaa =
DT::renderDT({
df =
structure(list(Col__1 = c("A", "A", "A", "A", "B", "C", "C",
"D", "G", "G", "H", "H", "J", "K", "K", "M", "M", "M", "M", "M",
"O", "P", "P", "R", "T", "T", "T", "U", "U", "W"), Col__2 = c(1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234), Col__3 = c(1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234), Col__4 = c(NA, 36, NA, 1,
4, 1, NA, 65, NA, 396, 7, 2, 3, 29, 4, 185, 651, NA, 1, NA, 2,
NA, 27, 92, 35, 29, NA, 60, 1, 144), Col__5 = c(NA, 1012, NA,
12, 350, 98, NA, 3925, NA, 4729, 327, 5, 87, 310, 30, 1854, 13013,
NA, 1, NA, 122, NA, 1354, 1629, 3278, 450, NA, 1808, 21, 948),
Col__6 = c(1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234), Col__7 = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
Col__8 = c(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), Col__9 = c("-",
" 36 (2%)", "-", " 1 (2%)", " 4 (1%)", " 1 (1%)", "-",
" 65 (1%)", "-", "396 (6%)", " 7 (1%)", " 2 (4%)", " 3 (2%)",
" 29 (4%)", " 4 (1%)", "185 (6%)", "651 (4%)", "-", " 1 (8%)",
"-", " 2 (1%)", "-", " 27 (2%)", " 92 (3%)", " 35 (1%)",
" 29 (3%)", "-", " 60 (2%)", " 1 (2%)", "144 (10%)"), Col__10 = c(2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5)), row.names = c(NA, -30L), class = "data.frame")
{ ### Few Important Definitions
Render3 = JS(
"function(data, type, row, meta){",
" if(type === 'sort' || type === 'type'){",
" return row[4];",
" } else {",
" return data;",
" }",
"}"
)
}
datatable(df,
callback = JS("$('table.dataTable.no-footer').css('border-bottom', '1px solid #b7b7b7');"),
options = list(pageLength = dim(df)[1], bLengthChange = FALSE, autoWidth = FALSE, bInfo = FALSE, searching = FALSE, info = FALSE, bPaginate = FALSE,
order = list(list(7, 'desc')),
columnDefs =
list(
list(width = '40%', targets = 1, className = 'dt-center dt-center1'),
list(width = '0%', targets = 2, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 3, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 4, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 5, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 6, className = 'dt-center dt-center1', visible = FALSE),
list(width = '15%', targets = 9, className = 'dt-center dt-center1', visible = TRUE, render = Render3),
list(targets = 0, visible = FALSE)
)))
})
}
shinyApp(ui, server)
As you see, if I scroll down of the container div, then I am losing the view of the header of the data.table. Is there any way to fix the position of the header in the current position?
Any pointer will be highly helpful.
Update after answer from Stéphane Laurent
Updated app with FixedHeader
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
tags$link(rel = "stylesheet", type = "text/css", href = "https://cdn.datatables.net/v/dt/dt-1.10.21/datatables.min.css"),
tags$script(src='https://cdn.datatables.net/v/dt/dt-1.10.21/fh-3.1.7/datatables.min.js'),
div(style = "height: 300px; width: 100%; overflow-y: scroll;", DTOutput("aaa", height = '400px'))
)
server <- function(input, output) {
output$aaa =
DT::renderDT({
df =
structure(list(Col__1 = c("A", "A", "A", "A", "B", "C", "C",
"D", "G", "G", "H", "H", "J", "K", "K", "M", "M", "M", "M", "M",
"O", "P", "P", "R", "T", "T", "T", "U", "U", "W"), Col__2 = c(1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234), Col__3 = c(1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234), Col__4 = c(NA, 36, NA, 1,
4, 1, NA, 65, NA, 396, 7, 2, 3, 29, 4, 185, 651, NA, 1, NA, 2,
NA, 27, 92, 35, 29, NA, 60, 1, 144), Col__5 = c(NA, 1012, NA,
12, 350, 98, NA, 3925, NA, 4729, 327, 5, 87, 310, 30, 1854, 13013,
NA, 1, NA, 122, NA, 1354, 1629, 3278, 450, NA, 1808, 21, 948),
Col__6 = c(1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234, 1234,
1234, 1234), Col__7 = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
Col__8 = c(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2), Col__9 = c("-",
" 36 (2%)", "-", " 1 (2%)", " 4 (1%)", " 1 (1%)", "-",
" 65 (1%)", "-", "396 (6%)", " 7 (1%)", " 2 (4%)", " 3 (2%)",
" 29 (4%)", " 4 (1%)", "185 (6%)", "651 (4%)", "-", " 1 (8%)",
"-", " 2 (1%)", "-", " 27 (2%)", " 92 (3%)", " 35 (1%)",
" 29 (3%)", "-", " 60 (2%)", " 1 (2%)", "144 (10%)"), Col__10 = c(2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
2.5, 2.5, 2.5, 2.5, 2.5)), row.names = c(NA, -30L), class = "data.frame")
{ ### Few Important Definitions
Render3 = JS(
"function(data, type, row, meta){",
" if(type === 'sort' || type === 'type'){",
" return row[4];",
" } else {",
" return data;",
" }",
"}"
)
}
datatable(df, extensions = "FixedHeader",
callback = JS("$('table.dataTable.no-footer').css('border-bottom', '1px solid #b7b7b7');"),
options = list(fixedHeader = TRUE,pageLength = dim(df)[1], bLengthChange = FALSE, autoWidth = FALSE, bInfo = FALSE, searching = FALSE, info = FALSE, bPaginate = FALSE,
order = list(list(7, 'desc')),
columnDefs =
list(
list(width = '40%', targets = 1, className = 'dt-center dt-center1'),
list(width = '0%', targets = 2, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 3, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 4, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 5, className = 'dt-center dt-center1', visible = FALSE),
list(width = '0%', targets = 6, className = 'dt-center dt-center1', visible = FALSE),
list(width = '15%', targets = 9, className = 'dt-center dt-center1', visible = TRUE, render = Render3),
list(targets = 0, visible = FALSE)
)))
})
}
shinyApp(ui, server)
I still dont see the header remained at fixed position under the current setup. However if I remove the styling overflow-y: scroll then it is working. Is there any workaround?
Does the FixedHeader extension suits your need?
library(DT)
datatable(iris, extensions = "FixedHeader",
options = list(
fixedHeader = TRUE
)
)

Array deducting 1 from previous element when repeated more than once

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]

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

Use Ruby to Truncate duplicate patterns in an Array

SITE ADMIN: WOULD YOU PLEASE REMOVE THIS POST?
For example, I have
tt = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0]
and I would like to slim it down to
tt_out = [0, 1, 1, 2, 2, 1, 1, 0, 0]
also I'd like to know when does the repetition begins and ends, hence I'd like to have the following tip
tip = '0','1.','.5','6.','.11','12.','.15','16.','.20'
tt = [0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0]
tip = []
tt_out = tt.map.with_index{|t, i|
start_range = (i==0 || tt[i-1] != tt[i])
end_range = (tt[i+1] != tt[i])
if start_range && end_range
tip << "#{i}"
elsif start_range
tip << "#{i}."
elsif end_range
tip << ".#{i}"
end
t if start_range || end_range
}.compact
tip
=> ["0", "1.", ".5", "6.", ".11", "12.", ".15", "16.", ".20"]
tt_out
=> [0, 1, 1, 2, 2, 1, 1, 0, 0]
P.S: You've got an error in your example, the last element of tip should be '.20'

Ruby find number of occurance within range

I have the following data where each row tells me a start and finish time of a process.
I would like to know from 12:20:00 to 14:00:00 with a step of 5 mins, I'd like to know how many processes running at each time instance. For example, there are 2 and 1 processes running at 12:30 and 12:35 respectively.
I'd like to implement this in Ruby 1.8 and what's the efficient Rubyiest way of doing this?
12:28:08, 12:33:29
12:28:20, 12:33:41
12:32:32, 12:32:44
12:36:56, 12:42:31
13:08:55, 13:09:08
14:09:00, 14:09:12
14:59:19, 15:04:37
15:41:40, 15:41:52
(Comments)
Ps: I have already got an array for the start time, sTime and end time, eTime. I want to do something like this:
(sTime..eTime).step($time_interval) do |cTime| # Current Time
cnt = 0
(0..(sessionstarttime.length-1)).each {|i| if cTime.between? (sessionstarttime[i], sessionendtime[i]); cnt += 1}
printf "%s, %d\n", cTime.strftime("%d/%m/%Y %H:%M:%S"), cnt
end
You can try this code (developed on 1.9 but should work on 1.8 as well):
a = %Q{
12:28:08, 12:33:29
12:28:20, 12:33:41
12:32:32, 12:32:44
12:36:56, 12:42:31
13:08:55, 13:09:08
14:09:00, 14:09:12
14:59:19, 15:04:37
15:41:40, 15:41:52
}
start = '12:20:00'
stop = '14:00:00'
require 'stringio'
def time_to_sec(time)
a = time.split(':').map(&:to_i)
a[0] * 3600 + a[1] * 60 + a[2]
end
def sec_to_time(sec)
h, n = sec.divmod 3600
m, s = n.divmod 60
"%02d:%02d:%02d" % [h, m, s]
end
rows = StringIO.new(a).read.delete(",").split("\n").reject{ |i| i.empty? }.map do |range|
range.split.map{ |time| time_to_sec(time) }
end
ranges = rows.map{ |i| i[0]..i[1] }
(time_to_sec(start)..time_to_sec(stop)).step(5*60) do |time|
cnt = ranges.count{|i| i.include? time}
puts "#{sec_to_time(time)}: #{cnt}"
end
Of course you don't need 'a' variable or StringIO if working with real files.
If you convert the values to a Time object (note I've assumed a date of 2000-01-01 for this example), you can do the following:
a= [
{ :s=> Time.utc(2000, 1, 1, 12, 28, 8), :e=> Time.utc(2000, 1, 1, 12, 33, 29) },
{ :s=> Time.utc(2000, 1, 1, 12, 28, 20), :e=> Time.utc(2000, 1, 1, 12, 33, 41) },
{ :s=> Time.utc(2000, 1, 1, 12, 32, 32), :e=> Time.utc(2000, 1, 1, 12, 32, 44) },
{ :s=> Time.utc(2000, 1, 1, 12, 36, 56), :e=> Time.utc(2000, 1, 1, 12, 42, 31) },
{ :s=> Time.utc(2000, 1, 1, 13, 8, 55), :e=> Time.utc(2000, 1, 1, 13, 9, 8) },
{ :s=> Time.utc(2000, 1, 1, 14, 9, 0), :e=> Time.utc(2000, 1, 1, 14, 9, 12) },
{ :s=> Time.utc(2000, 1, 1, 14, 59, 19), :e=> Time.utc(2000, 1, 1, 15, 4, 37) },
{ :s=> Time.utc(2000, 1, 1, 15, 41, 40), :e=> Time.utc(2000, 1, 1, 15, 41, 52) }
]
checkTime = Time.utc(2000, 1, 1, 12, 32, 40)
a.delete_if{|b| #b[:s] is start time, b[:e] is end time
(b[:s] > checkTime) || (b[:e] < checkTime)
}
Here are a couple of simple objects that model something that should calculate what you need. This gives you a start to an interface you can use to do more complex logic if you need it.
require 'time'
# Object Definitions
class ProcessTimelineEntry
def initialize(start_time, end_time)
#start_time = start_time
#end_time = end_time
end
def running_at?(time)
time >= #start_time && time < #end_time
end
end
class ProcessTimeline
def initialize()
#entries = []
end
def add_entry(start_time, end_time)
#entries << ProcessTimelineEntry.new(start_time, end_time)
end
def process_count_at(time)
#entries.count { |e| e.running_at?(time) }
end
end
# Example Usage
timeline = ProcessTimeline.new
DATA.readlines.each do |line|
start_time, end_time = line.split(', ')
timeline.add_entry(Time.parse(start_time), Time.parse(end_time))
end
puts timeline.process_count_at(Time.parse("12:30"))
puts timeline.process_count_at(Time.parse("12:35"))
__END__
12:28:08, 12:33:29
12:28:20, 12:33:41
12:32:32, 12:32:44
12:36:56, 12:42:31
13:08:55, 13:09:08
14:09:00, 14:09:12
14:59:19, 15:04:37
15:41:40, 15:41:52
Here is a solution which will scale better to large numbers of start-stop pairs or time steps than the other posted answers (given that you want to know the number of processes running during each time step, not just 1 or 2 selected time steps):
START = Time.utc(2000,1,1, 12,20,0).to_i
FINISH = Time.utc(2000,1,1, 14,0,0).to_i
STEP = 60*5 # 5 minutes
result = Array.new(((FINISH-START).to_f/STEP).ceil, 0)
processes = %Q{
12:28:08, 12:33:29
12:28:20, 12:33:41
12:32:32, 12:32:44
12:36:56, 12:42:31
13:08:55, 13:09:08
14:09:00, 14:09:12
14:59:19, 15:04:37
15:41:40, 15:41:52 }
processes.each_line do |times|
times =~ /(\d\d):(\d\d):(\d\d), (\d\d):(\d\d):(\d\d)/
st = Time.utc(2000,1,1, $1.to_i,$2.to_i,$3.to_i).to_i
fin = Time.utc(2000,1,1, $4.to_i,$5.to_i,$6.to_i).to_i
st = START if st < START
fin = END if fin > END
(st..fin).step(STEP) do |t|
result[(t-START)/STEP] += 1
end
end
The count of how many processes were running during each time step will be left in result. You can put an object wrapper around it if desired to provide a nice interface.

Resources