How to change a key-value pair of a hash in Ruby? - arrays

I have a query that returns an array and a hash.
How can I change the hash and add a new key-value pair to it: import_id: 1, cost: 0, or can I use a map method on the query?
The query is as follows.
name = Store.joins(:paid => :supply).group(:name).select("supply.name").where("stores.identifier IN (?) ", tids).pluck(:supply_id, :name)
The array is as follows.
[[258, "Square"], [245, "App"]]
When I convert it to a hash, it returns the following.
{258=>"Square", 245=>"App"}
The desired output is as follows.
{{supply_id: 258, name: "Square", import_id: 1, cost: 0}, {supply_id: 245, name: "App", import_id: 1, cost: 0}}

It appears the response is an array of [supply_id, name] pairs. You can easily destructure the array and restructure the data as hashes with proper symbolic keys.
array = [[258, "Square"], [245, "App"]]
array.map do |(id, name)|
{ supply_id: id, name: name, import_id: 1, cost: 0 }
end
# [{:supply_id=>258, :name=>"Square", :import_id=>1, :cost=>0}, ...]

Use #select instead of #pluck and call .as_json or .map(&:attributes)
Store.joins(:paid => :supply).group(:name)
.select("supply.name").where("stores.identifier IN (?) ", tids)
.select(:supply_id, :name).as_json
# [{supply_id: 258, name: "Square"}, {supply_id: 245, name: "App"}]
or
Store.joins(:paid => :supply).group(:name)
.select("supply.name").where("stores.identifier IN (?) ", tids)
.select(:supply_id, :name).map(&:attributes)
# [{supply_id: 258, name: "Square"}, {supply_id: 245, name: "App"}]
or you can construct Hash with {import_id: 1, cost: 0} appended
Store.joins(:paid => :supply).group(:name)
.select("supply.name").where("stores.identifier IN (?) ", tids)
.select(:supply_id, :name)
.map {|e| {supply_id: e.supply_id, name: e.name, import_id: 1, cost: 0} }
# [{supply_id: 258, name: "Square", import_id: 1, cost: 0}, {supply_id: 245, name: "App", import_id: 1, cost: 0}]
or
You can use Hash#merge during Hash generation step to include {import_id: 1, cost: 0}
hash.merge({import_id: 1, cost: 0})
# To achieve: {{supply_id: 258, name: "Square", import_id: 1, cost: 0}, {supply_id: 245, name: "App", import_id: 1, cost: 0}}

Related

Mongodb - how to aggregate with "bucket"

that's my dataset:
[
{_id: "628d54495995f1e6589675b3", age: 52, sex: 1, cp: 0, trestbps: 125,
chol: 212, fbs: 0, restecg: "1", thalach: 168, exang: "0", oldpeak: 1},
{_id: "628d54495995f1e6589675b4", age: 53, sex: 1, cp: 1, trestbps: 140,
chol: 203, fbs: 1, restecg: "0", thalach: 155, exang: "1", oldpeak: 3.1},
]
... and so on
I want to group for 2 classes of age (under 40 (included) and over 40 (not included)) and for each class to determine which value of cp is the most frequent.
cp can have four different values: 0, 1, 2, 3
I thought bucket could be the right operator, but I can't get it to work
Thanks for helping

How to iterate through an array of objects in ruby

I have this array right here and I need to get the "id" of each object
[{ id: 1, points: 60 }, { id: 2, points: 20 }, { id: 3, points: 95 }, { id: 4, points: 75 }]
customers = [{ id: 1, points: 90 }, { id: 2, points: 20 }, { id: 3, points: 70 }, { id: 4, points: 40 }, { id: 5, points: 60 }, { id: 6, points: 10}]
I know how to go through the whole array with
#scores.each_with_index{ |score, index| }
However, I haven't found a way to get the objects's points.
Perhaps you are looking for the following.
customers = [
{ id: 1, points: 90 }, { id: 2, points: 20 },
{ id: 3, points: 70 }, { id: 4, points: 40 },
{ id: 5, points: 60 }, { id: 6, points: 10}
]
h = customers.each_with_object({}) do |g,h|
id, points = g.values_at(:id, :points)
h[id] = points
end
#=> {1=>90, 2=>20, 3=>70, 4=>40, 5=>60, 6=>10}
This allows you to easily extract information of interest, such as the following.
h.keys
#=> [1, 2, 3, 4, 5, 6]
h.values
#=> [90, 20, 70, 40, 60, 10]
h[2]
#=> 20
h.key?(5)
#=> true
h.key?(7)
#=> false
h.value?(70)
#=> true
h.value?(30)
#=> false
What you called score is actually an hash like { id: 1, points: 60 } and I'm going to call it item
So, let's try
#scores.each_with_index do |item, index|
puts "#{index + 1}: id #{item[:id]}, points #{item[:points]}"
end
So, I have this array right here and I need to get the id of each object
In order to transform each element of a collection, you can use Enumerable#map (or in this case more precisely Array#map):
customers.map { _1[:id] }
#=> [1, 2, 3, 4, 5, 6]
This given construct is an array of objects so we need to individually iterate through each element and print out the value present in the objects. The following code shows how we can do it:
customers.each{|obj| p obj[:id].to_s+" "+ obj[:points].to_s }
Here we iterate through each element and print out individual entities of the hash using the obj[:id]/obj[:points] (obj being each individual object here.)
What about something like this?
customers.map(&:to_proc).map{ |p| [:id, :points].map(&p) }
=> [[1, 90], [2, 20], [3, 70], [4, 40], [5, 60], [6, 10]]

Update values in array of hashes conditionally

results = [
{
:id=>2,
:start=> "3:30",
break: 30,
num_attendees: 14,
hello: {hi: 1}
},
{
id: 3,
start: "3: 40",
break: 40,
num_attendees: {hi: 2},
hello: 4
},
{
id: 4,
start: "4: 40",
break: 10,
num_attendees: 40
}
]
Is there a better way of doing this by avoiding multiple iteration?
results.each do |hash|
hash.each { |k, v|
hash[k] = "" if v.is_a? Hash
}
end
=> [{:id=>2, :start=>"3:30", :break=>30, :num_attendees=>14, :hello=>""}, {:id=>3, :start=>"3: 40", :break=>40, :num_attendees=>"", :hello=>4}, {:id=>4, :start=>"4: 40", :break=>10, :num_attendees=>40}]
Thanks

JS - group, map and flatten array of objects with arrays in them

Dears,
I really am not able to achieve following result:
{"2002":[1,2,3...]},
{"2003":[1,2,3,4...]},
...
I have following data (short example below):
{ "Year": "28-01-2020", "numbers": [10, 12, 20, 32, 35, 37] },
{ "Year": "03-10-2019", "numbers": [1, 6, 16, 19, 20, 30] },
{ "Year": "11-01-2018", "numbers": [14, 21, 25, 27, 30, 39] },
{ "Year": "11-08-2015", "numbers": [8, 16, 17, 18, 38, 46] },
I managed to use lodash _.groupBy to achieve following mid result:
[{…}]
0:
2000: Array(4)
0:
Year: "2000"
numbers: (7) [empty, 6, 25, 27, 37, 48, 49]
Year: "2000"
numbers: (7) [empty, 7, 12, 19, 30, 45, 49]
2: {Year: "2000", numbers: Array(7)}
2001: Array(104)
[0 … 99]
[100 … 103]
100: {Year: "2001", numbers: Array(7)}
101: {Year: "2001", numbers: Array(7)}
[0 … 99]
0: {Year: "2002", numbers: Array(7)}
1: {Year: "2002", numbers: Array(7)}
..
but i would like to have one object per year, with all numbers that appeared in sub arrays in this year
Could you please help me, i have tired ES6 map and for in loops, but non give me the proper result
Thank you in advance
This worked for me:
const slicedYear = results.map(elem => (elem = { Year: elem.Year.slice(elem.Year.length - 4), numbers: elem.numbers }));
const groupedResults = _.groupBy(slicedYear, 'Year');
for (let key in groupedResults) {
const selectedNumbers = { [key]: [groupedResults[key].map(elem => elem.numbers)].flat(2) };
}

best way to sort an array of objects by category and infinite subcategory

I have a databases that I will be pulling the array of objects below. I want to create a tree structure from it.
When the parent_id is nil then its a top level Category. If the parent_id is not nil then it a subcategory of the id value of parent_id.
The best solution I have come up with was to loop through the set to get the top level categories then continue looping through until I have organized. Ultimately the table will be less than 500 records but there is no guarantee of that. So looping over and over seems really stupid. However, I can't think of another way to do it. Below is a sample dataset and the way it would be organized.
[{id: 1, name: "top test 1", parent_id: nil},
{id: 2, name: "test 2", parent_id: 1},
{id: 3, name: "test 3", parent_id: 1},
{id: 4, name: "top test 4", parent_id: nil},
{id: 5, name: "test 5", parent_id: 3},
{id: 6, name: "test 6", parent_id: 4},
{id: 7, name: "test 7", parent_id: 4}]
top test 1
test 2
test 3
test 5
top test 2
test 6
test 7
Actual array of objects returned from the db. Still just test data.
[#<ItemsCategory id: 2, name: "test 2", parent_id: 1, created_at: "2014-03-04 17:58:46", updated_at: "2014-03-04 17:58:46">,
#<ItemsCategory id: 3, name: "test 3", parent_id: 1, created_at: "2014-03-04 17:23:23", updated_at: "2014-03-04 17:23:23">,
#<ItemsCategory id: 5, name: "test 4", parent_id: 3, created_at: "2014-03-06 17:48:25", updated_at: "2014-03-06 17:48:25">,
#<ItemsCategory id: 1, name: "NEW test EDITED", parent_id: nil, created_at: "2014-03-04 17:57:21", updated_at: "2014-03-10 20:50:10">]
You can do it like this:
Code
def doit(data, indent = 2)
d = data.each_with_object({}) { |h,g| g[h[:id]] = h }
d.each {|_,h| h[:ancestor_ids] =
(h[:top_level_category_id] ? d[h[:parent_id]][:ancestor_ids] :[])+[h[:id]]}
.values
.sort_by { |h| h[:ancestor_ids] }
.each { |h| puts ' '*((h[:ancestor_ids].size-1)*indent) + "#{h[:name]}" }
end
Demo
data=[
{id: 1, name: "parent test 1", parent_id: nil, top_level_category_id: nil},
{id: 2, name: "test 2", parent_id: 1, top_level_category_id: 1},
{id: 3, name: "test 3", parent_id: 1, top_level_category_id: 1},
{id: 4, name: "parent test 4", parent_id: nil, top_level_category_id: nil},
{id: 5, name: "test 5", parent_id: 3, top_level_category_id: 4},
{id: 6, name: "test 6", parent_id: 4, top_level_category_id: 4},
{id: 7, name: "test 7", parent_id: 4, top_level_category_id: 4}
]
doit(data)
parent test 1
test 2
test 3
test 5
parent test 4
test 6
test 7
Explanation
What we need to do is add another hash element (whose key I've named :ancestor_ids), whose value is an array of the hash's :id and those of all of its ancestors; i.e., we want to add the following elements to the respective hashes:
:ancestor_ids => [1]
:ancestor_ids => [1,2]
:ancestor_ids => [1,3]
:ancestor_ids => [4]
:ancestor_ids => [1,3,5]
:ancestor_ids => [4,6]
:ancestor_ids => [4,7]
Once we have these, we can use sort_by { |h| h[:ancestor_ids] } to put the elements of the array data in the proper order. (If you are uncertain how the elements of an array are ordered, review Array#<=>.) Also h[:ancestor_ids].size is used to determine the amount of indentation required when displaying the results.
The calculations go like this*:
d = data.each_with_object({}) { |h,g| g[h[:id]] = h }
#=> {1=>{:id=>1, :name=>"parent test 1",...},
# 2=>{:id=>2, :name=>"test 2",...},
# 3=>{:id=>3, :name=>"test 3",...},
# 4=>{:id=>4, :name=>"parent test 4",...},
# 5=>{:id=>5, :name=>"test 5",...},
# 6=>{:id=>6, :name=>"test 6",...},
# 7=>{:id=>7, :name=>"test 7",...}}
We perform this step to make it easy to find the rows of data that correspond to a record's parent.
e = d.each {|_,h| h[:ancestor_ids] =
(h[:top_level_category_id] ? d[h[:parent_id]][:ancestor_ids]:[])+[h[:id]]}
#=> {1=>{:id=>1,...,:ancestor_ids=>[1]},
# 2=>{:id=>2,...,:ancestor_ids=>[1, 2]},
# 3=>{:id=>3,...,:ancestor_ids=>[1, 3]},
# 4=>{:id=>4,...,:ancestor_ids=>[4]}
# 5=>{:id=>5,...,:ancestor_ids=>[1, 3, 5]},
# 6=>{:id=>6,...,:ancestor_ids=>[4, 6]},
# 7=>{:id=>7,...,:ancestor_ids=>[4, 7]}}
This adds the element whose key is :ancestor_ids. We no longer need the keys, so we will extract the values, sort them by :ancestor_ids and display the results:
f = e.values
#=> [{:id=>1,...,:ancestor_ids=>[1]},
# {:id=>2,...,:ancestor_ids=>[1, 2]},
# {:id=>3,...,:ancestor_ids=>[1, 3]},
# {:id=>4,...,:ancestor_ids=>[4]}
# {:id=>5,...,:ancestor_ids=>[1, 3, 5]},
# {:id=>6,...,:ancestor_ids=>[4, 6]},
# {:id=>7,...,:ancestor_ids=>[4, 7]}}
g = f.sort_by { |h| h[:ancestor_ids] }
#=> [{:id=>1,...,:ancestor_ids=>[1]},
# {:id=>2,...,:ancestor_ids=>[1, 2]},
# {:id=>3,...,:ancestor_ids=>[1, 3]},
# {:id=>5,...,:ancestor_ids=>[1, 3, 5]},
# {:id=>4,...,:ancestor_ids=>[4]}
# {:id=>6,...,:ancestor_ids=>[4, 6]},
# {:id=>7,...,:ancestor_ids=>[4, 7]}}
indent = 2
g.each { |h| puts ' '*((h[:ancestor_ids].size-1)*indent) + "#{h[:name]}" }
parent test 1
test 2
test 3
test 5
parent test 4
test 6
test 7
Points
Do you need the hash element whose key is :top_level_category_id, considering that :parent_id => nil for top level elements?
Production code would raise an exception if, in the calculation of e above, there were no element of d with key h[:parent_id] or the value h[:parent_id] had no key :ancestor_ids.
This answer relies on the assumption that, for each element h of Data that is not top level, h[:id] > h[:parent_id] when h[:parent_id] is not nil. If the rows of Data are not initially ordered by :id, they must be sort_by'ed :id as a first step.
*
If you try running this at home, it should work from the command line, but IRB and PRY cannot handle the continued lines that begin with a dot
Requires a single pass through the edge list. All nodes must fit in memory together; edge list must constitute an actual tree (that is, there's no checking for forests, proper DAGs, or cycles).
private static final Long DUMMY = null;
Node buildTree( Iterable< ? extends Edge > iedg ) {
Map< Long, Node > mnod = new HashMap< Long, Node >();
for ( Edge edg : iedg )
getNode( mnod, iedg.getParentId() ).addChild(
getNode( mnod, iedg.getId() ).withName( iedg.getName() )
);
return getNode( mnod, DUMMY ).firstChild();
}
private Node getNode( Map< Long, Node > mnod, Long lId ) {
Node nod = mnod.get( lId );
if ( null == nod )
mnod.put( lId, nod = new Node().withId( lId ) );
return nod;
}

Resources