I have an array like this:
[["vietnam", "Hồ Chí Minh", "ho-chi-minh", 131],
["vietnam", "Hà Nội", "ha-noi", 96],
["lao", "Vien", "vien", 26],
["thailand", "Bangkok", "bangkok", 11],
["vietnam", "Đồng Nai", "dong-nai", 10],
["china", "Shanghai", "shanghia", 8],
["lao", "ABC", "abcc", 24],
["vietnam", "Long An", "long-an", 6]]
and i want to generate a hash function with key based on first element of array elements as follows
{:vietnam =>
[["Hồ Chí Minh", "ho-chi-minh", 131],
["Hà Nội", "ha-noi", 96],
["Long An", "long-an", 6]],
:lao =>
[["Vien", "vien", 26],
["ABC", "abcc", 24]],
:thailand =>
[["Bangkok", "bangkok", 11]],
:china =>
[["Shanghai", "shanghai", 8]]
}
how can i do this?
Use the combined power of group_by and Array#shift
array = [["vietnam", "Hồ Chí Minh", "ho-chi-minh", 131],
["vietnam", "Hà Nội", "ha-noi", 96],
["lao", "Vien", "vien", 26],
["thailand", "Bangkok", "bangkok", 11],
["vietnam", "Đồng Nai", "dong-nai", 10],
["china", "Shanghai", "shanghia", 8],
["lao", "ABC", "abcc", 24],
["vietnam", "Long An", "long-an", 6]
]
hash = array.group_by { |e| e.shift }
Output
=> {
"vietnam" => [["Hồ Chí Minh", "ho-chi-minh", 131], ["Hà Nội", "ha-noi", 96], ["Đồng Nai", "dong-nai", 10], ["Long An", "long-an", 6]],
"lao" => [["Vien", "vien", 26], ["ABC", "abcc", 24]],
"thailand" => [["Bangkok", "bangkok", 11]],
"china" => [["Shanghai", "shanghia", 8]]
}
You can use the ruby group_by method:
array = [["vietnam", "Hồ Chí Minh", "ho-chi-minh", 131],
["vietnam", "Hà Nội", "ha-noi", 96],
["lao", "Vien", "vien", 26],
["thailand", "Bangkok", "bangkok", 11],
["vietnam", "Đồng Nai", "dong-nai", 10],
["china", "Shanghai", "shanghia", 8],
["lao", "ABC", "abcc", 24],
["vietnam", "Long An", "long-an", 6]]
hash = array.group_by { |a| a[0] }
# {"vietnam"=>[["vietnam", "Hồ Chí Minh", "ho-chi-minh", 131], ["vietnam", "Hà Nội", "ha-noi", 96], ["vietnam", "Đồng Nai", "dong-nai", 10], ["vietnam", "Long An", "long-an", 6]], "lao"=>[["lao", "Vien", "vien", 26], ["lao", "ABC", "abcc", 24]], "thailand"=>[["thailand", "Bangkok", "bangkok", 11]], "china"=>[["china", "Shanghai", "shanghia", 8]]}
hash.each { |k, v| hash[k] = v.map { |arr| arr.drop(1) } }
# {"vietnam"=>[["Hồ Chí Minh", "ho-chi-minh", 131], ["Hà Nội", "ha-noi", 96], ["Đồng Nai", "dong-nai", 10], ["Long An", "long-an", 6]], "lao"=>[["Vien", "vien", 26], ["ABC", "abcc", 24]], "thailand"=>[["Bangkok", "bangkok", 11]], "china"=>[["Shanghai", "shanghia", 8]]}
Related
I am using highcharts library to plot Sankey chart with our data, which has many number of nodes in each column. My issue is links width is reduces when it is between one of nodes on the top and one of the nodes are a bit lower than that. More longer the link is more width reduces.
I am wondering if there is any work around or some simple thing from documentation that I am missing. Any help is appreciated. Below is the fiddle
https://jsfiddle.net/Saibabu276/cqj2n413/
Highcharts.chart('container', {
title: {
text: 'Highcharts Sankey Diagram'
},
chart: {
height: 1000
},
accessibility: {
point: {
valueDescriptionFormat: '{index}. {point.from} to {point.to}, {point.weight}.'
}
},
series: [{
keys: ['from', 'to', 'weight'],
data: [
['Brazil', 'Portugal', 5],
['Brazil', 'France', 1],
['Brazil', 'Spain', 1],
['Brazil', 'England', 1],
['Canada', 'Portugal', 1],
['Canada', 'France', 5],
['Canada', 'England', 1],
['Mexico', 'Portugal', 1],
['Mexico', 'France', 1],
['Mexico', 'Spain', 5],
['Mexico', 'England', 1],
['USA', 'Portugal', 1],
['USA', 'France', 1],
['USA', 'Spain', 1],
['USA', 'England', 5],
['Portugal', 'Angola', 2],
['Portugal', 'Senegal', 1],
['Portugal', 'Morocco', 1],
['Portugal', 'South Africa', 3],
['France', 'Angola', 1],
['France', 'Senegal', 3],
['France', 'Mali', 3],
['France', 'Morocco', 3],
['France', 'South Africa', 1],
['Spain', 'Senegal', 1],
['Spain', 'Morocco', 3],
['Spain', 'South Africa', 1],
['England', 'Angola', 1],
['England', 'Senegal', 1],
['England', 'Morocco', 2],
['England', 'South Africa', 7],
['South Africa', 'China', 5],
['South Africa', 'India', 1],
['South Africa', 'Japan', 3],
['Angola', 'China', 5],
['Angola', 'India', 1],
['Angola', 'Japan', 3],
['Senegal', 'China', 5],
['Senegal', 'India', 1],
['Senegal', 'Japan', 3],
['Mali', 'China', 5],
['Mali', 'India', 1],
['Mali', 'Japan', 3],
['Morocco', 'China', 5],
['Morocco', 'India', 1],
['Morocco', 'Japan', 3]
],
type: 'sankey',
name: 'Sankey demo series'
}]
});
To set the connection between nodes option series.sankey.curveFactor add a possibility to make the line straight completely or change the curve to lover.
Higher numbers makes the links in a sankey diagram or dependency wheelrender more curved. A curveFactor of 0 makes the lines straight.
plotOptions: {
sankey: {
nodeWidth: 10,
curveFactor: 0.1
}
},
Live demo:
https://jsfiddle.net/BlackLabel/6ac2syzx/
I'm trying to create a function to complete the sequence of hours in the following hash.
{
name: "cardio",
data: [["06:00", 999], ["09:00", 154], ["10:00", 1059], ["11:00", 90]]
}
It should create all the missing values in the field data
["07:00", 0], ["08:00", 0], ["12:00", 0], ["13:00", 0] ... ["23:00", 0]
Expected result:
{
name: "cardio",
data: [["06:00", 999], ["07:00", 0], ["08:00", 0], ["09:00", 154], ["10:00", 1059], ["11:00", 90]], ["12:00", 0], ["13:00", 0] ... ["23:00", 0]
}
Is it possible to do that? Something like:
data.each do |row|
(6..23).each do |hour|
.....
end
end
You can make use of Array#assoc to do something like this:
Searches through an array whose elements are also arrays comparing obj with the first element of each contained array using obj.==.
input = {
name: "cardio",
data: [["06:00", 999], ["09:00", 154], ["10:00", 1059], ["11:00", 90]]
}
input[:data] = 24.times.collect do |hour|
hour = "%02d:00" % hour
input[:data].assoc(hour) || [hour, 0]
end
puts input.inspect
# {:name=>"cardio", :data=>[["00:00", 0], ["01:00", 0], ["02:00", 0], ["03:00", 0], ["04:00", 0], ["05:00", 0], ["06:00", 999], ["07:00", 0], ["08:00", 0], ["09:00", 154], ["10:00", 1059], ["11:00", 90], ["12:00", 0], ["13:00", 0], ["14:00", 0], ["15:00", 0], ["16:00", 0], ["17:00", 0], ["18:00", 0], ["19:00", 0], ["20:00", 0], ["21:00", 0], ["22:00", 0], ["23:00", 0]]}
Data
h = {name:"cardio", data:[["06:00", 999], ["09:00", 154], ["10:00", 1059], ["11:00", 90]]}
first = 7
last = 23
Code
mdata = (first..last).each_with_object(h[:data].to_h) { |hour,g|
g["%02d:00" % hour] ||= 0 }.sort
#=> [["06:00", 999], ["07:00", 0], ["08:00", 0], ["09:00", 154], ["10:00", 1059],
# ["11:00", 90], ["12:00", 0],..., ["21:00", 0], ["23:00", 0]]
h.merge(h).merge(data: mdata)
#=> {:name=>"cardio",
# :data=>[["06:00", 999], ["07:00", 0], ["08:00", 0], ["09:00", 154],
# ["10:00", 1059], ["11:00", 90], ["12:00", 0],...["23:00", 0]]
I have this array of arrays in the data attribute for each activity corresponding to the class attendance by hour.
I need the total attendance of each activity by hour.
Is it possible to group and sum it?
Example:
The correct result for the hour 7 is 50+60 = 110
[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
Expected result:
:data=>[["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]
Case 1: The values of :data are arrays of the same size whose elements (two-element arrays) are ordered the same by their first elements
arr = [{ :name=>"cardio", :data=>[["06", 999], ["07", 50], ["08", 0]] },
{ :name=>"swimming", :data=>[["06", 0], ["07", 60], ["08", 0]] }]
a, b = arr.map { |h| h[:data].transpose }.transpose
#=> [[["06", "07", "08"], ["06", "07", "08"]], [[999, 50, 0], [0, 60, 0]]]
{ :data=>a.first.zip(b.transpose.map { |col| col.reduce(:+) }) }
#=> {:data=>[["06", 999], ["07", 110], ["08", 0]]}
Case 2: The values of :data are arrays which may differ in size and whose elements (two-element arrays) may not be ordered the same by their first element
arr = [{ :name=>"cardio", :data=>[["05", 999], ["07", 50], ["08", 0]] },
{ :name=>"swimming", :data=>[["08", 300], ["04", 33], ["07", 60]] }]
{ :data=>arr.flat_map { |g| g[:data] }.
each_with_object(Hash.new(0)) { |(f,v),h| h[f] += v }.
sort.
to_a }
#=> {:data=>[["04", 33], ["05", 999], ["07", 110], ["08", 300]]}
Note:
depending on requirements, sort may not be required
the second method could be used regardless of whether the values of :data are defined in parallel
the second method uses the form of Hash::new which takes an argument (the default value) which here is zero. This is sometimes called a counting hash. See the doc for details.
How to group and sum values in array of arrays with the same structure
You should do it like this :
[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
Try this one:
act.map{|h| h[:data]}.flatten(1).group_by(&:first).map { |k,v| [k, v.map(&:last).inject(:+)] }
# => [["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]
Hope this will help you
Given:
act=[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
{:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
You can do:
> act.map{ |h| h[:data] }
.flatten(1)
.group_by{ |h,n| h }
.map { |k,v| [k, v.map(&:last).sum] }
=> [["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]
Or,
> act.map{|h| h[:data]}
.flatten(1)
.each_with_object(Hash.new(0)) {|e,h| h[e[0]]+=e[1]}.to_a
works too.
actions=[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
{:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
actions.map{|act| act[:data]}.reduce({}) do |acc, data|
acc.merge(data.to_h) {|k, v1, v2| v1 + v2}
end.to_a
This solution takes advantage of the fact that Ruby's Hash keeps the insertion order.
The merit of this algorithm is that it allows missing keys in either data array.
I have the following search results (json) that I need to tie with a model (I expanded the first one to make it more readable:
{"products_matched":
{ "89": //<-- this is a product ID
{ "lines": {
"0": {
"meta_count": 6,
"metas": {
"0": [0, 4], "1": [0, 4], "2": [0, 1], "3": [1, 2], "4": [2, 3], "5": [3, 4]
}
},
"1": {
"meta_count": 5,
"metas": {
"0": [0, 4], "1": [0, 4], "2": [0, 1], "3": [1, 2], "4": [2, 3]
}
}
},
"product_score": 0.0
} ,
"82": {"lines": {"0": {"meta_count": 2, "metas": {"0": [0, 4], "1": [0, 4]}}}, "product_score": 0.55},
"60": {"lines": {"0": {"meta_count": 3, "metas": {"0": [0, 4], "1": [0, 4], "2": [3, 4]}}}, "product_score": 0.0},
"10": {"lines": {"0": {"meta_count": 2, "metas": {"0": [0, 4], "1": [0, 4]}}}, "product_score": 0.0}}
}
In rails, how can I return both the model object and the associated meta data (lines) attached to id?
What I have so far:
product_ids = results["products_matched"].keys # => ["89", "82", "60", "10"]
products = Product.where(id: product_ids) # => #<ActiveRecord::Relation [#<Product id: 82, name: ...],[...] ...>
I could try to collect / map them together under one instance variable, or I could send them separately, but that leaves the view to marry the two.
Simple solution, and I don't know if it is the best, but I just added an attr_accessor to the Product model.
attr_accessor :result
Then I loop through the products:
product_ids = results["products_matched"].keys # => ["89", "82", "60", "10"]
#products = Product.where(id: product_ids)
#products.each do |p|
p.result = results["parsers_matched"][p.id.to_s]
end
I am trying to adapt a highcharts heatmap from raw coding to the Yii2 plugin by Milos Schuman. Area and line charts are working but there is not an example of using heatmap and I tried almost everything.
This is the code as shown in highcharts demo:
$(function () {
$('#container').highcharts({
chart: {
type: 'heatmap',
},
title: {
text: 'Sales per employee per weekday'
},
xAxis: {
categories: ['Alexander', 'Marie', 'Maximilian', 'Sophia', 'Lukas', 'Maria', 'Leon', 'Anna', 'Tim', 'Laura']
},
yAxis: {
categories: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
},
series: [{
name: 'Sales per employee',
borderWidth: 1,
data: [[0, 0, 10], [0, 1, 19], [0, 2, 8], [0, 3, 24], [0, 4, 67], [1, 0, 92], [1, 1, 58], [1, 2, 78], [1, 3, 117], [1, 4, 48], [2, 0, 35], [2, 1, 15], [2, 2, 123], [2, 3, 64], [2, 4, 52], [3, 0, 72], [3, 1, 132], [3, 2, 114], [3, 3, 19], [3, 4, 16], [4, 0, 38], [4, 1, 5], [4, 2, 8], [4, 3, 117], [4, 4, 115], [5, 0, 88], [5, 1, 32], [5, 2, 12], [5, 3, 6], [5, 4, 120], [6, 0, 13], [6, 1, 44], [6, 2, 88], [6, 3, 98], [6, 4, 96], [7, 0, 31], [7, 1, 1], [7, 2, 82], [7, 3, 32], [7, 4, 30], [8, 0, 85], [8, 1, 97], [8, 2, 123], [8, 3, 64], [8, 4, 84], [9, 0, 47], [9, 1, 114], [9, 2, 31], [9, 3, 48], [9, 4, 91]],
dataLabels: {
enabled: true,
color: '#000000'
}
}]
});
});
And a sample of Area chart using the Yii2 widget:
use miloschuman\highcharts\Highcharts;
echo Highcharts::widget([
'options' => [
'title' => ['text' => 'Fruit Consumption'],
'xAxis' => [
'categories' => ['Apples', 'Bananas', 'Oranges']
],
'yAxis' => [
'title' => ['text' => 'Fruit eaten']
],
'series' => [
['name' => 'Jane', 'data' => [1, 0, 4]],
['name' => 'John', 'data' => [5, 7, 3]]
]
]
]);
The problem basically is that in case of a heatmap chart it is required to specify that the heatmap module is needed before the options description like that:
'scripts' => [
'modules/heatmap', // adds heatmap support
],
That is a working sample based on the highcharts demo:
$heatmap_options = [
'scripts' => [
'modules/heatmap', // adds heatmap support
],
'options' => [
'title' => ['text' => 'Sales per employee per weekday'],
'chart' => [
'type' => 'heatmap'
],
'xAxis' => [
'categories' =>['Alexander', 'Marie', 'Maximilian', 'Sophia', 'Lukas', 'Maria', 'Leon', 'Anna', 'Tim', 'Laura']
],
'yAxis' => [
'categories' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
],
'colorAxis' =>[
],
'series' => [[
'name' => 'Sales per employee',
'borderWidth' => 1,
'data' => [[0, 0, 10], [0, 1, 19], [0, 2, 8], [0, 3, 24], [0, 4, 67], [1, 0, 92], [1, 1, 58], [1, 2, 78], [1, 3, 117],
[1, 4, 48], [2, 0, 35], [2, 1, 15], [2, 2, 123], [2, 3, 64], [2, 4, 52], [3, 0, 72], [3, 1, 132], [3, 2, 114],
[3, 3, 19], [3, 4, 16], [4, 0, 38], [4, 1, 5], [4, 2, 8], [4, 3, 117], [4, 4, 115], [5, 0, 88], [5, 1, 32], [5, 2, 12],
[5, 3, 6], [5, 4, 120], [6, 0, 13], [6, 1, 44], [6, 2, 88], [6, 3, 98], [6, 4, 96], [7, 0, 31], [7, 1, 1], [7, 2, 82],
[7, 3, 32], [7, 4, 30], [8, 0, 85], [8, 1, 97], [8, 2, 123], [8, 3, 64], [8, 4, 84], [9, 0, 47], [9, 1, 114], [9, 2, 31],
[9, 3, 48], [9, 4, 91]],
'dataLabels' => [
'enabled' => true,
'color' => '#000000'
]
]
]
]
];
echo Highcharts::widget($heatmap_options);