I have some data in this format
[{
"_id" => "20",
"value" => 1
}, {
"_id" => "19",
"value" => 1
}, {
"_id" => nil,
"value" => 8
}, {
"_id" => "27",
"value" => 1
}, {
"_id" => "25",
"value" => 3
}, {
"_id" => "28",
"value" => 1
}]
I want to merge the same values with "_id" key and sum the "value" values.
Desire output
[{
"_id" => "20",
"value" => 1
}, {
"_id" => "19",
"value" => 2
}, {
"_id" => nil,
"value" => 8
}, ...]
There is an elegant way to do this?
I have tried with two loops but I think that is not the best way to do it.
As with most things in Ruby, a trip to the Enumerable documentation turns up the group_by method which can help group things together by some arbitrary criteria. Combine that with something that does the sums and you get this:
v.group_by do |e|
e['_id']
end.map do |id, list|
{
'_id' => id,
'value' => list.inject(0) { |sum, e| sum + e['value'] }
}
end
# => [{"_id"=>"20", "value"=>1}, {"_id"=>"19", "value"=>2}, {"_id"=>nil, "value"=>28},
# {"_id"=>"27", "value"=>1}, {"_id"=>"25", "value"=>3}, {"_id"=>"28", "value"=>1},
# {"_id"=>"23", "value"=>1}, {"_id"=>"16", "value"=>1}, {"_id"=>"18", "value"=>2},
# {"_id"=>"22", "value"=>2}]
arr = [{ "_id" => "20", "value" => 1 },
{ "_id" => "19", "value" => 1 },
{ "_id" => nil, "value" => 8 },
{ "_id" => "20", "value" => 1 },
{ "_id" => "25", "value" => 3 },
{ "_id" => "19", "value" => 1 },
]
h = arr.each_with_object(Hash.new(0)) { |g,h| h[g["_id"]] += g["value"] }
#=> {"20"=>2, "19"=>2, nil=>8, "25"=>3}
If you instead want to return an array of hashes with unique values for "_id" and the values of "value" updated, you could first compute h above, then
arr.uniq { |g| g["_id"] }.map { |g| g.update("_id"=>h[g["_id"]]) }
#=> [{"_id"=>"20", "value"=>2}, {"_id"=>" 19", "value"=>2},
# {"_id"=>nil, "value"=>8}, {"_id"=>"25", "value"=>3}]
This uses the methods Array#uniq with a block, Enumerable#map and Hash#update (aka merge!).
Alternatively, you could write the following.
arr.each_with_object({}) { |g,h|
h.update(g["_id"]=>g) { |_,o,n| o.merge("value"=>o["value"]+n["value"]) } }.values
#=> [{"_id"=>"20", "value"=>2}, {"_id"=>" 19", "value"=>2},
# {"_id"=>nil, "value"=>8}, {"_id"=>"25", "value"=>3}]
Again, I've used Hash#update, but this time I have employed a block to determine the values of keys that are present in both hashes being merged. See also Enumerable#each_with_object and Hash#merge. Note that, as arguments, (k=>v) is shorthand for ({ k=>v }).
Related
I have an array:
arr = ["Bar", "abc", "foo", "1", "20”, "10", "_def"]
I need to sort using case-insensitive alphabetically first, then numerically followed by special characters.
I am trying to use sort_by:
irb(main):071:0> arr.sort_by {|s| [s[/[0-9a-z]+/], s.to_i]}
=> ["1", "10", "20", "abc", "Bar", "_def", "foo"]
The output has to be:
arr = ["abc", "Bar", "foo", "1", “10”, “20", "_def"]
From the docs:
Arrays are compared in an “element-wise” manner; the first element of ary is compared with the first one of other_ary using the <=> operator, then each of the second elements, etc…
You can take advantage of this behavior by creating sorting groups:
arr = ["Bar", "abc", "foo", "1", "20", "10", "_def"]
arr.sort_by do |s|
case s
when /^[a-z]/i
[1, s.downcase]
when /^\d/
[2, s.to_i]
else
[3, s]
end
end
#=> ["abc", "Bar", "foo", "1", "10", "20", "_def"]
The first element (1, 2, 3) defines the group's position: strings with letters on 1st position, numeric strings on 2nd position and the remaining on 3rd position. Within each group, the elements are sorted by the second element: strings with letters by their lowercase value, numeric strings by their integer value and the remaining by themselves.
You can create groups first and then sort groups.
arr.each_with_object(Array.new(3) { Array.new }) do |word, group|
if word.match /^[A-Za-z]/
group.first
elsif word.match /^[0-9]/
group.second
else
group.third
end << word
end.flat_map{ |group| group.sort_by{ |x| x.downcase } }
#=> ["abc", "Bar", "foo", "1", "10", "20", "_def"]
A little benchmark is needed:
require 'active_support/core_ext/array/access.rb'
require 'fruity'
ARR = ["Bar", "abc", "foo", "1", "20", "10", "_def"]
def run_demir(ary)
ary.each_with_object(Array.new(3) { Array.new }) do |word, group|
if word.match /^[A-Za-z]/
group.first
elsif word.match /^[0-9]/
group.second
else
group.third
end << word
end.flat_map{ |group| group.sort_by{ |x| x.downcase } }
end
def run_stefan(ary)
ary.sort_by do |s|
case s
when /^[a-z]/i
[1, s.downcase]
when /^\d/
[2, s.to_i]
else
[3, s]
end
end
end
run_demir(ARR) # => ["abc", "Bar", "foo", "1", "10", "20", "_def"]
run_stefan(ARR) # => ["abc", "Bar", "foo", "1", "10", "20", "_def"]
compare do
demir { run_demir(ARR) }
Stefan { run_stefan(ARR) }
end
Which results in:
# >> Running each test 512 times. Test will take about 1 second.
# >> Stefan is faster than demir by 2x ± 0.1
In my ruby on rails application am getting below data from API service, the data format is array of hashes like below.
data = [
{"category": "Population.Behaviors.Commute", "tag": "away", "description": "Work Outside the Home"},
{"category": "Population.Behaviors.Commute.Vehicle", "tag": "mbike", "description": "Bike to Work"}
]
The above code format I have to convert to the below format for generating the form elements.
response_format = [
{
"label": "Population",
"options": [
{
"label": "Behaviors",
"options": [
{
"label": "Commute",
"options": [
{
"label": "Vehicle",
"options": [
{
"tag": "mbike",
"description": "Bike to Work"
}
]
},
{
"tag": "away",
"description": "Work Outside the Home"
}
]
}
]
}
]
}
]
Anyone kindly help to achieve the solution.
All you need is to recursively build an inner hash:
data.
each_with_object(Hash.new { |h, k| h[k] = h.dup.clear }) do |h, acc|
(h[:category].split('.').
reduce(acc) do |inner, cat|
inner["label"] = cat
inner["options"] ||= {}
end || {}).
merge!("tag" => h[:tag], "description" => h[:description])
end
#⇒ {
# "label" => "Population",
# "options" => {
# "label" => "Behaviors",
# "options" => {
# "label" => "Commute",
# "options" => {
# "description" => "Work Outside the Home",
# "label" => "Vehicle",
# "options" => {
# "description" => "Bike to Work",
# "tag" => "mbike"
# },
# "tag" => "away"
# }
# }
# }
# }
array = [
[ 1, "name1" ],
[ 2, "name2" ],
[ 3, "name3" ],
[ 4, "name4" ]
]
I want to make this as an array of hashes like this:
array_hash = [{ "id" => 1, "name" => "name1" },
{ "id" => 2, "name" => "name2" },
{ "id" => 3, "name" => "name3" },
{ "id" => 4, "name" => "name4" }]
array = [
[ 1, "name1" ],
[ 2, "name2" ],
[ 3, "name3" ],
[ 4, "name4" ]
]
array.map { |e| ['id', 'name'].zip(e).to_h }
#⇒ [
# {"id"=>1, "name"=>"name1"},
# {"id"=>2, "name"=>"name2"},
# {"id"=>3, "name"=>"name3"},
# {"id"=>4, "name"=>"name4"}
# ]
The only interesting here is Enumerable#zip, that “merges” arrays.
I'd use:
array.map { |id, name| { 'id' => id, 'name' => name } }
#=> [{"id"=>1, "name"=>"name1"},
# {"id"=>2, "name"=>"name2"},
# {"id"=>3, "name"=>"name3"},
# {"id"=>4, "name"=>"name4"}]
The .to_h method is new to Ruby 2.x. Here is an alternative for anyone on 1.9.x or lower.
array = [[ 1, "name1" ], [ 2, "name2" ], [ 3, "name3" ], [ 4, "name4" ]]
array.inject([]) { |a, r| a << { id: r[0], name: r[1] } }
I have a little problem. Can you tell me why permit method cannot return data_set_users_attributes and data_set_synch_agents_attributes in second example from code below? This is weird. All data looks fine, permit arguments too. Both parameters_hash are send from this same form and both permit data are created by this same function.
EDIT: I use Ruby 2.3.0 and rails 4.2.4
require 'rails/all'
# Params passes
_permit = [
"configuration_id",
"max_packages_on_server",
"reports_synch_interval_min",
{
"data_set_users_attributes" => [
{
"0" => [
"user_id",
"send_alerts",
"_destroy"
]
},
{
"2015_09_09_15_21_50" => [
"user_id",
"send_alerts",
"_destroy"
]
}
]
},
{
"data_set_synch_agents_attributes" => [
{
"0" => [
"synch_agent_id",
"max_idle_on_data_set",
"_destroy"
]
},
{
"2015_09_09_15_21_51" => [
"synch_agent_id",
"max_idle_on_data_set",
"_destroy"
]
}
]
}
]
parameters_hash = {
"action" => "create",
"commit" => "Save",
"controller" => "data_sets",
"data_set" => {
"configuration_id" => "",
"data_set_synch_agents_attributes" => {
"0" => {
"_destroy" => "false",
"max_idle_on_data_set" => "-1",
"synch_agent_id" => "1"
},
"2015_09_09_15_21_51" => {
"_destroy" => "",
"max_idle_on_data_set" => "-1",
"synch_agent_id" => "2"
}
},
"data_set_users_attributes" => {
"0" => {
"_destroy" => "false",
"send_alerts" => "1",
"user_id" => "1"
},
"2015_09_09_15_21_50" => {
"_destroy" => "",
"send_alerts" => "1",
"user_id" => "2"
}
},
"max_packages_on_server" => "",
"reports_synch_interval_min" => ""
}
}
params = ActionController::Parameters.new(parameters_hash)
p params.require(:data_set).permit(_permit)
# Params not passes
_permit = [
"configuration_id",
"max_packages_on_server",
"reports_synch_interval_min",
{
"data_set_users_attributes" => [
{
"0" => [
"user_id",
"send_alerts",
"_destroy"
]
},
{
"1" => [
"user_id",
"send_alerts",
"_destroy"
]
}
]
},
{
"data_set_synch_agents_attributes" => [
{
"0" => [
"synch_agent_id",
"max_idle_on_data_set",
"_destroy"
]
},
{
"1" => [
"synch_agent_id",
"max_idle_on_data_set",
"_destroy"
]
}
]
}
]
parameters_hash = {
"action" => "create",
"commit" => "Save",
"controller" => "data_sets",
"data_set" => {
"configuration_id" => "",
"data_set_synch_agents_attributes" => {
"0" => {
"_destroy" => "false",
"max_idle_on_data_set" => "-1",
"synch_agent_id" => "1"
},
"1" => {
"_destroy" => "false",
"max_idle_on_data_set" => "-1",
"synch_agent_id" => "2"
}
},
"data_set_users_attributes" => {
"0" => {
"_destroy" => "false",
"send_alerts" => "1",
"user_id" => "1"
},
"1" => {
"_destroy" => "false",
"send_alerts" => "1",
"user_id" => "2"
}
},
"max_packages_on_server" => "",
"reports_synch_interval_min" => ""
}
}
params = ActionController::Parameters.new(parameters_hash)
p params.require(:data_set).permit(_permit)
Any help will be appreciated.
Best regards
If I get it right, you need to try this:
params.require(:data_set).permit(
:max_packages_on_server,
:reports_synch_interval_min,
data_set_synch_agents_attributes: [:synch_agent_id, :max_idle_on_data_set, :_destroy],
data_set_users_attributes: [:send_alerts, :user_id, :_destroy])
to get parameters for updating your model.
I have objects that look like:
[<ltree_val: "1", contents: "blah">,
<ltree_val: "1.1", contents: "blah">,
<ltree_val: "1.1.1", contents: "blah">,
<ltree_val: "2", contents: "blah">,
<ltree_val: "2.1", contents: "blah">]
Where ltree_val determines their tree structure.
I need to generate something like...
[{ "data" : "1",
"children" :
[{ "data" : "1.1",
"children" :
[{ "data" : "1.1.1" }]
}]
},
{ "data" : "2" }]
Where I have children which are determined by an ltree value, which are themselves elements of the same object.
If I sort these objects by their ltree value, how can I create nested entries?
I'm open to either RABL or JBuilder. I'm totally lost.
The answer was to use a recursive function...
# encoding: UTF-8
def json_ltree_builder( json, ltree_item )
json.title t( ltree_item.title )
json.attr do
json.id ltree_item.id
end
json.metadata do
json.val1 ltree_item.val1
json.val2 ltree_item.val2
end
children = ltree_item.children
unless children.empty?
json.children do
json.array! children do |child|
json_ltree_builder( json, child )
end
end
end
end
json.array! #menu_items do |menu_item|
json_ltree_builder( json, menu_item )
end
This builds something like
[
{ "title":"Title 1",
"attr" : {
"id": 111
},
"data" : {
"val1" : "Value 1",
"val2" : "Value 2"
},
"children" : [
{
"title":"Child 1",
"attr" : {
"id": 112
},
"data" : {
"val1" : "Value 1",
"val2" : "Value 2"
}
},
{
"title":"Child 2",
"attr" : {
"id": 112
},
"data" : {
"val1" : "Value 1",
"val2" : "Value 2"
}
}
]
}
]