Hi I have this array for example
array = [
{ingredients: [:t1, :t2], exp: 100, result: :t5},
{ingredients: [:t3, :t4], exp: 200, result: :t10},
{ingredients: [:t1, :t2], exp: 50, result: :t6}
]
I want an array to look like this:
array = [
{ingredients: [:t1, :t2], exp: 100, results: [:t5, :t6]},
{ingredients: [:t3, :t4], exp: 200, results: [:t10]},
]
So it should check every element in the array and combine all the results of elements which contain the same ingredients array.
I don't really know where to start with this, so any help is appreciated.
array = [
{ingredients: [:t1, :t2], exp: 100, result: :t5},
{ingredients: [:t3, :t4], exp: 200, result: :t10},
{ingredients: [:t1, :t2], exp: 50, result: :t6}
]
array.reduce([]) { |memo, e| # will build new array
el = memo.find { |_e| _e[:ingredients] == e[:ingredients] }
if el # already have same ingredients
el[:results] << e[:result] # modify
memo
else
e[:results] = [*e[:result]] # append
e.delete :result
memo << e
end
}
#=> [
# [0] {
# :exp => 100,
# :ingredients => [
# [0] :t1,
# [1] :t2
# ],
# :results => [
# [0] :t5,
# [1] :t6
# ]
# },
# [1] {
# :exp => 200,
# :ingredients => [
# [0] :t3,
# [1] :t4
# ],
# :results => [
# [0] :t10
# ]
# }
#]
Hope it helps.
Related
Sorry, it's a little bit too long. I need to change the status of :hero and :heroine to "dead"
def update_status
epic_tragedy = {
:montague => {
:patriarch => {name: "Lord Montague", age: "53"},
:matriarch => {name: "Lady Montague", age: "54"},
:hero => {name: "Romeo", age: "15", status: "alive"},
:hero_friends => [
{name: "Benvolio", age: "17", attitude: "worried"},
{name: "Mercutio", age: "18", attitude: "hot-headed"}
]
},
:capulet => {
:patriarch => {name: "Lord Capulet", age: "50"},
:matriarch => {name: "Lady Capulet", age: "51"},
:heroine => {name: "Juliet", age: "15", status: "alive"},
:heroine_friends => [
{name: "Steven", age: "30", attitude: "confused"},
{name: "Nurse", age: "44", attitude: "worried"}
]
}
}
I did add the code below inside the method but it shows error.
epic_tragedy[:montague][:hero][:status] = "dead"
epic_tragedy[:capulet][:hero][:status] = "dead"
Is there any additional step before I put those lines?
epic_tragedy is a local variable within the method definition. Such local variables cannot be accessed from outside the method.
However, the hash is returned when the method is executed, so try
h = update_status
#=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"alive"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"alive"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }
h[:montague][:hero][:status] = "dead"
h[:capulet][:heroine][:status] = "dead"
h #=> {:montague=>{
# :patriarch=>{:name=>"Lord Montague", :age=>"53"},
# :matriarch=>{:name=>"Lady Montague", :age=>"54"},
# :hero=>{:name=>"Romeo", :age=>"15", :status=>"dead"},
# :hero_friends=>[
# {:name=>"Benvolio", :age=>"17", :attitude=>"worried"},
# {:name=>"Mercutio", :age=>"18", :attitude=>"hot-headed"}
# ]
# },
# :capulet=>{
# :patriarch=>{:name=>"Lord Capulet", :age=>"50"},
# :matriarch=>{:name=>"Lady Capulet", :age=>"51"},
# :heroine=>{:name=>"Juliet", :age=>"15", :status=>"dead"},
# :heroine_friends=>[
# {:name=>"Steven", :age=>"30", :attitude=>"confused"},
# {:name=>"Nurse", :age=>"44", :attitude=>"worried"}
# ]
# }
# }
I want to transform the given array into result array:
given = [{
"foo_v1_4" => [{
"derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => {
"three" => 0.65
},
"bazbar" => {
"three" => 0.65
}
}
}
}]
}]
# the value of key :one is first hash key (foo_v1_4) plus underscore (_) plus derivate_version (0)
result = [{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
What I tried:
given.each do |el |
el.each do |derivat |
derivat.each do |d |
d.each do |layer |
layer.each do |l |
derivat = "#{d}_#{l['derivate_version']}"
puts derivat
end
end
end
end
end
I'm struggling at iterating through "layers" hash, the amount of elements in layers is equal to the amount of elements in result array.
It helps to format the objects so we can better see their structures:
given = [
{
"foo_v1_4" => [
{ "derivate_version" => 0,
"layers" => {
"tlayer" => {
"baz" => { "three" => 0.65 },
"bazbar" => { "three" => 0.65 }
}
}
}
]
}
]
result = [
{
one: 'foo_v1_4_0',
tlayer: 'baz',
three: '0.6'
},
{
one: 'foo_v1_4_0',
tlayer: 'bazbar',
three: '0.6'
}
]
We can begin by writing the structure of result:
result = [
{
one:
tlayer:
three:
},
{
one:
tlayer:
three:
}
]
We see that
given = [ { "foo_v1_4" => <array> } ]
The values of the keys :one in the hash result[0] is therefore the first key of the first element of given:
one_val = given[0].keys[0]
#=> "foo_v1_4"
result = [
{
one: one_val
tlayer:
three:
},
{
one: one_val
tlayer:
three:
}
]
All the remaining objects of interest are contained in the hash
h = given[0]["foo_v1_4"][0]["layers"]["layer"]
#=> {
# "baz"=>{ "three"=>0.65 },
# "bazbar"=>{ "three"=>0.65 }
# }
so it is convenient to define it. We see that:
h.keys[0]
#=> "baz"
h.keys[1]
#=> "bazaar"
h["bazbar"]["three"]
#=> 0.65
Note that it generally is not good practice to assume that hash keys are ordered in a particular way.
We may now complete the construction of result,
v = h["bazbar"]["three"].truncate(1)
#=> 0.6
result = [
{
one: one_val,
tlayer: h.keys[0],
three: v
},
{ one: one_val,
tlayer: h.keys[1],
three: v
}
]
#=> [
# { :one=>"foo_v1_4", :tlayer=>"baz", :three=>0.6 },
# { :one=>"foo_v1_4", :tlayer=>"bazbar", :three=>0.6 }
# ]
The creation of the temporary objects one_val, h, and v improves time- and space-efficiency, makes the calculations easier to test and improves the readability of the code.
Try the below:
result = []
given.each do |level1|
level1.each do |key, derivate_versions|
derivate_versions.each do |layers|
# iterate over the elements under tlayer
layers.dig('layers', 'tlayer').each do |tlayer_key, tlayer_value|
sub_result = {}
# key - foo_v1_4, layers['derivate_version'] - 0 => 'foo_v1_4_0'
sub_result[:one] = key + '_' + layers['derivate_version'].to_s
# talyer_key - baz, barbaz
sub_result[:tlayer] = tlayer_key
# talyer_value - { "three" => 0.65 }
sub_result[:three] = tlayer_value['three']
result << sub_result
end
end
end
end
The value of result will be:
2.6.3 :084 > p result
[{:one=>"foo_v1_4_0", :tlayer=>"baz", :three=>0.65}, {:one=>"foo_v1_4_0", :tlayer=>"bazbar", :three=>0.65}]
I have a nested array that I want to sort by a specific object, some advice would be very much appreciated.
In this example I'd like the output to return sorted by the dates that are nested.
arr = [
[
{
"log"=>[
[
"2016-09-03T00:00:00-03:00",
],
[
"2016-09-01T00:00:00-03:00",
],
[
"2016-09-02T00:00:00-03:00",
]
]
}
]
]
arr = [
[
{
"log"=>[
["2016-09-03T00:00:00-03:00"],
["2016-09-01T00:00:00-03:00"],
["2016-09-02T00:00:00-03:00"]
]
}
]
]
To return a sorted array and not mutate arr:
[[{ "log"=>arr[0][0]["log"].sort_by(&:first) }]]
#=> [[{"log"=>[
# ["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]
# ]}]]
To sort in place:
arr[0][0]["log"] = arr[0][0]["log"].sort_by(&:first)
#=> [["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]]
arr
#=> [[{"log"=>[
# ["2016-09-01T00:00:00-03:00"],
# ["2016-09-02T00:00:00-03:00"],
# ["2016-09-03T00:00:00-03:00"]
# ]}]]
I have the hash below:
mm = {
0 => {
0 => 'p1',
1 => 'p2',
2 => 'p3'
},
1 => {
0 => 'idfp1',
1 => 'idfp2',
2 => 'idfp3'
},
2 => {
0 => 'idfp12',
1 => 'idfp22',
2 => 'idfp32'
}
}
And i'm trying to sort it by the hash with a key of 0. In the first hash (0), there are k-v pairs of number to identifier.
In every subsequent hash (1 and 2), 0 points to the 0 from the first hash, 1 points to the 1 from the first hash, etc.
In each hash after 0 (1 and 2), there are IDs (id for person 1) that belong to p1 (person 1).
I've tried to sort this by creating a new hash with only the first hash in the one above to no avail. This is my attempt. The keys are correct but it's pointing to nil, when it should be pointing to the hash with each person's id.
ids = {}
org = {}
mm[0].each do |id, name|
ids[id] = name
end
mm.drop(1).each do |one|
one.each do |key, id|
org[ids[key]] = id
end
end
How can I achieve this in Ruby?
Edit:
In case the explanation doesn't suffice, here is the desired result:
org = {
'p1' => {
0 => 'idfp1',
1 => 'idfp12'
},
'p2' => {
0 => 'idfp2',
1 => 'idfp22'
},
'p3' => {
0 => 'idfp3',
1 => 'idfp32'
}
}
Two ways:
#1
Code
mm[0].invert.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
Explanation
a = mm[0]
#=> {0=>"p1", 1=>"p2", 2=>"p3"}
b = a.invert
#=> {"p1"=>0, "p2"=>1, "p3"=>2}
b.each_with_object({}) { |(k,i),h|
h[k] = (1...mm.size).each_with_object ({}) { |j,g| g[j] = mm[j][i] } }
#=> {"p1"=>{1=>"idfp1", 2=>"idfp12"},
# "p2"=>{1=>"idfp2", 2=>"idfp22"},
# "p3"=>{1=>"idfp3", 2=>"idfp32"}}
#2
Code
mm.values
.map(&:values)
.transpose
.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}
Explanation
a = mm.values
#=> [{0=>"p1", 1=>"p2", 2=>"p3" },
# {0=>"idfp1", 1=>"idfp2", 2=>"idfp3" },
# {0=>"idfp12", 1=>"idfp22", 2=>"idfp32"}]
b = a.map(&:values
#=> [[ "p1", "p2", "p3" ],
# [ "idfp1", "idfp2", "idfp3" ],
# [ "idfp12", "idfp22", "idfp32"]]
c = b.transpose
#=> [["p1", "idfp1", "idfp12"],
# ["p2", "idfp2", "idfp22"],
# ["p3", "idfp3", "idfp32"]]
c.each_with_object({}) { |a,h| h[a.shift] = Hash[[*(0...a.size)].zip(a) ] }
#=> {"p1"=>{0=>"idfp1", 1=>"idfp12"},
# "p2"=>{0=>"idfp2", 1=>"idfp22"},
# "p3"=>{0=>"idfp3", 1=>"idfp32"}}
Say I have a CSV file with 4 fields,
ID,name,pay,age
and about 32,000 records.
What's the best way to stick this into a hash in Ruby?
In other words, an example record would look like:
{:rec1 => {:id=>"00001", :name => "Bob", :pay => 150, :age => 95 } }
Thanks for the help!
You can use the Excelsior rubygem for this:
csv = ...
result = Hash.new
counter = 1
Excelsior::Reader.rows(csv) do |row|
row_hash = result[("rec#{counter}".intern)] = Hash.new
row.each do |col_name, col_val|
row_hash[col_name.intern] = col_val
end
counter += 1
end
# do something with result...
Typically we'd want to use an :id field for the Hash key, since it'd be the same as a primary key in a database table:
{"00001" => {:name => "Bob", :pay => 150, :age => 95 } }
This will create a hash looking like that:
require 'ap'
# Pretend this is CSV data...
csv = [
%w[ id name pay age ],
%w[ 1 bob 150 95 ],
%w[ 2 fred 151 90 ],
%w[ 3 sam 140 85 ],
%w[ 31999 jane 150 95 ]
]
# pull headers from the first record
headers = csv.shift
# drop the first header, which is the ID. We'll use it as the key so we won't need a name for it.
headers.shift
# loop over the remaining records, adding them to a hash
data = csv.inject({}) { |h, row| h[row.shift.rjust(5, '0')] = Hash[headers.zip(row)]; h }
ap data
# >> {
# >> "00001" => {
# >> "name" => "bob",
# >> "pay" => "150",
# >> "age" => "95"
# >> },
# >> "00002" => {
# >> "name" => "fred",
# >> "pay" => "151",
# >> "age" => "90"
# >> },
# >> "00003" => {
# >> "name" => "sam",
# >> "pay" => "140",
# >> "age" => "85"
# >> },
# >> "31999" => {
# >> "name" => "jane",
# >> "pay" => "150",
# >> "age" => "95"
# >> }
# >> }
Check out the Ruby Gem smarter_csv, which parses CSV-files and returns array(s) of hashes for the rows in the CSV-file. It can also do chunking, to more efficiently deal with large CSV-files, so you can pass the chunks to parallel Resque workers or mass-create records with Mongoid or MongoMapper.
It comes with plenty of useful options - check out the documentation on GitHub
require 'smarter_csv'
filename = '/tmp/input.csv'
array = SmarterCSV.process(filename)
=>
[ {:id=> 1, :name => "Bob", :pay => 150, :age => 95 } ,
...
]
See also:
https://github.com/tilo/smarter_csv
http://www.unixgods.org/~tilo/Ruby/process_csv_as_hashes.html
Hash[*CSV.read(filename, :headers => true).flat_map.with_index{|r,i| ["rec#{i+1}", r.to_hash]}]