Joining arrays of hashes in Ruby - ruby

I am trying to join multiple arrays of hashes in ruby using a common key. For example:
country_info = [
{country_id: "US", country_desc: "United States"},
{country_id: "AU", country_desc: "Australia"}
]
country_stats = [
{country_id:"US", pageviews: 150},
{country_id:"AU", pageviews: 200}
]
i_want = [
{country_id: "US", country_desc: "United States", pageviews:150},
{country_id: "AU", country_desc: "Australia", pageviews:200}
]
This is something like the pv.nest function of protovis in Javascript. See: http://protovis-js.googlecode.com/svn/trunk/jsdoc/symbols/pv.Nest.html
how can I do this in Ruby?

If you put all the different hashes into one array, you can use group_by to group together those with the same country_id. You can then use inject with merge to merge those together:
country_info_and_stats = country_info + country_stats
country_info_and_stats.group_by {|x| x[:country_id]}.map do |k,v|
v.inject(:merge)
end
#=> [{:country_id=>"US", :country_desc=>"United States", :pageviews=>150},
# {:country_id=>"AU", :country_desc=>"Australia", :pageviews=>200}]

Related

How do I convert a word to phonetic alphabet in Ruby?

I am trying write a code to convert a word into phonetic alphabet. The desired return value should be something like this:
# >> Type your name and I will convert it to Phonetic Alphabets!
Kevin
# >> Kilo Echo Victor India November
I wrote a hash.
puts "Type your name and I will convert it to Phonetic Alphabets!"
name = gets.chomp
nato_keys = {
"A": "Alpha", "B": "Bravo", "C": "Charlie",
"D": "Delta", "E": "Echo", "F": "Foxtrot",
"G": "Golf", "H": "Hotel", "I": "India",
"J": "Juliett","K": "Kilo", "L": "Lima",
"M": "Mike", "N": "November","O": "Oscar",
"P": "Papa", "Q": "Quebec", "R": "Romeo",
"S": "Sierra", "T": "Tango", "U": "Uniform",
"V": "Victor", "W": "Whiskey", "X": "X-ray",
"Y": "Yankee", "Z": "Zulu"
}
def nato()
puts name.nato_keys.upcase().join(" ")
end
I have an issue with my method as it triggers an error.
Since no one mentioned it, here's a solution with values_at (this assumes string keys in the substitutions hash, as they should be).
str = "Kevin"
nato_keys.values_at(*str.upcase.chars).join(' ')
Assuming the number of words to converted is more than 26 it makes sense to prepare the appropriate hash before doing any conversions.
H = nato_keys.transform_keys(&:to_s)
#=> {"A"=>"Alpha", "B"=>"Bravo",..., "Z"=>"Zulu"}
word = gets.chomp
Supose
word = "Kevin"
Then
word.upcase.each_char.map { |c| H[c] }.join(' ')
#=> "Kilo Echo Victor India November"
You would want to do this:
name.chars.map { |x| nato_keys[x.upcase.to_sym] }.join(' ')
This name.chars give the enumeration with each character of the string name, map iterates on each character and transforms it to the instruction in the block.
block { |x| nato_keys[x.upcase.to_sym] }, picks the relevant mapping for the character, but before that, it converts the key to uppercase and symbol(because in your nato_keys, keys are symbols). 'a' will become :A.
After the block manipulation, we join the result with ' ' to give resultant string.
name.upcase.chars.map(&:intern).map(&nato_keys).join(' ')
First we upcase the entire string. We then split the string into each individual char and turn each into a symbol. Then we can map over the chars sending them one at a time to the nato_keys element reference [], returning the phonetic word, and converting the name. Finally, join them with spaces to format the new string.
EDIT: When using Hash element references we can call the Hash itself.
As Cary Swoveland says, the hash nato_keys, which really does not make sense, has to be converted to one that makes sense:
nato_keys.transform_keys!(&:to_s)
Given:
name = "Kevin"
then,
name.gsub(/(\A)?./){"#{" " unless $1}#{nato_keys[$&.upcase]}"}
# => "Kilo Echo Victor India November"
I would start with preparing more robust hash to transform
transform =
nato_keys.
flat_map do |k, v|
v = v + ' '
[[k.to_s, v], [k.to_s.downcase, v]]
end.
to_h.
tap { |h| h.default_proc = ->(h, k) { h[k] = k } }
Now you might transform your words as easy as:
"Kevin".gsub(/./, transform).strip
#⇒ "Kilo Echo Victor India November"

How to sort nested hash in ruby?

example_hash = {
"1" => {"occurence": 12, "display": ""},
"2" => {"occurence": 15, "display": ""},
"3" => {"occurence": 16, "display": ""}
}
For the given above nested hash, how do we sort (descending) it based on the occurrence value. The result should display the keys in the sorted order [3,2,1]
example_hash.sort_by { |_,h| -h[:occurrence] }
#=> [["3", {:occurrence=>16, :display=>""}],
# ["2", {:occurrence=>15, :display=>""}],
# ["1", {:occurrence=>12, :display=>""}]]
Tack on .to_h if a hash is desired, but one does not normally desire a hash with keys sorted in a particular way.
In console, this:
example_hash.sort{|a,b| b[1][:occurence] <=> a[1][:occurence]}.to_h
returns this:
{"3"=>{:occurence=>16, :display=>""}, "2"=>{:occurence=>15, :display=>""}, "1"=>{:occurence=>12, :display=>""}}
BTW, I believe you've misspelled 'occurence'.

Sort keys in Ruby hash without changing the associated values from their original positions

I'm having trouble sorting a Hash that looks like this:
{9=>["Blake Johnson", "Jack Bauer"],
7=>["Bart Simpson", "Homer Simpson"],
10=>["Avi Flombaum", "Jeff Baird"]}
and I would like it to look like this:
{7=>["Blake Johnson", "Jack Bauer"],
9=>["Bart Simpson", "Homer Simpson"],
10=>["Avi Flombaum", "Jeff Baird"]}
The requirement is quite weird, but this should work:
hash = {
9 => ["Blake Johnson", "Jack Bauer"],
7 => ["Bart Simpson", "Homer Simpson"],
10 => ["Avi Flombaum", "Jeff Baird"]
}
hash.keys.sort.zip(hash.values).to_h
#=> {7=>["Blake Johnson", "Jack Bauer"], 9=>["Bart Simpson", "Homer Simpson"], 10=>["Avi Flombaum", "Jeff Baird"]}
hash = {
9 => ["Blake Johnson", "Jack Bauer"],
7 => ["Bart Simpson", "Homer Simpson"],
10 => ["Avi Flombaum", "Jeff Baird"]
}
Hash[hash.sort]
{ 7=>["Bart Simpson", "Homer Simpson"],
9=>["Blake Johnson", "Jack Bauer"],
10=>["Avi Flombaum", "Jeff Baird"]
}
You could do something like this:
sorted_hash = {}
hash.sort.each do |key_value_pair|
key = key_value_pair[0]
value = key_value_pair[1]
sorted_hash[key] = value
end
The sort method returns a sorted arrays of arrays where each element of these inner arrays are [key, {value}] and they are sorted by the key (if it is a sortable object).

Merging hash values in an array of hashes based on key

I have an array of hashes similar to this:
[
{"student": "a","scores": [{"subject": "math","quantity": 10},{"subject": "english", "quantity": 5}]},
{"student": "b", "scores": [{"subject": "math","quantity": 1 }, {"subject": "english","quantity": 2 } ]},
{"student": "a", "scores": [ { "subject": "math", "quantity": 2},{"subject": "science", "quantity": 5 } ] }
]
Is there a simpler way of getting the output similar to this except looping through the array and finding a duplicate and then combining them?
[
{"student": "a","scores": [{"subject": "math","quantity": 12},{"subject": "english", "quantity": 5},{"subject": "science", "quantity": 5 } ]},
{"student": "b", "scores": [{"subject": "math","quantity": 1 }, {"subject": "english","quantity": 2 } ]}
]
Rules for merging duplicate objects:
Students are merged on matching "value" (e.g. student "a", student "b")
Students scores on identical subjects are added (e.g. student a's math scores 2 and 10 become 12 when merged)
Is there a simpler way of getting the output similar to this except looping through the array and finding a duplicate and then combining them?
Not that I know of. IF you explain where this data is comeing form the answer may be different but just based on the Array of Hash objects I think you will haev to iterate and combine.
While it is not elegant you could use a solution like this
arr = [
{"student"=> "a","scores"=> [{"subject"=> "math","quantity"=> 10},{"subject"=> "english", "quantity"=> 5}]},
{"student"=> "b", "scores"=> [{"subject"=> "math","quantity"=> 1 }, {"subject"=> "english","quantity"=> 2 } ]},
{"student"=> "a", "scores"=> [ { "subject"=> "math", "quantity"=> 2},{"subject"=> "science", "quantity"=> 5 } ] }
]
#Group the array by student
arr.group_by{|student| student["student"]}.map do |student_name,student_values|
{"student" => student_name,
#combine all the scores and group by subject
"scores" => student_values.map{|student| student["scores"]}.flatten.group_by{|score| score["subject"]}.map do |subject,subject_values|
{"subject" => subject,
#combine all the quantities into an array and reduce using `+`
"quantity" => subject_values.map{|h| h["quantity"]}.reduce(:+)
}
end
}
end
#=> [
{"student"=>"a", "scores"=>[
{"subject"=>"math", "quantity"=>12},
{"subject"=>"english", "quantity"=>5},
{"subject"=>"science", "quantity"=>5}]},
{"student"=>"b", "scores"=>[
{"subject"=>"math", "quantity"=>1},
{"subject"=>"english", "quantity"=>2}]}
]
I know that you specified your expected result but I wanted to point out that making the output simpler makes the code simpler.
arr.map(&:dup).group_by{|a| a.delete("student")}.each_with_object({}) do |(student, scores),record|
record[student] = scores.map(&:values).flatten.map(&:values).each_with_object(Hash.new(0)) do |(subject,score),obj|
obj[subject] += score
obj
end
record
end
#=>{"a"=>{"math"=>12, "english"=>5, "science"=>5}, "b"=>{"math"=>1, "english"=>2}}
With this structure getting the students is as easy as calling .keys and the scores would be equally as simple. I am thinking something like
above_result.each do |student,scores|
puts student
scores.each do |subject,score|
puts " #{subject.capitalize}: #{score}"
end
end
end
The console out put would be
a
Math: 12
English: 5
Science: 5
b
Math: 1
English: 2
There are two common ways of aggregating values in such instances. The first is to employ the method Enumerable#group_by, as #engineersmnky has done in his answer. The second is to build a hash using the form of the method Hash#update (a.k.a. merge!) that uses a block to resolve the values of keys which are present in both of the hashes being merged. My solution uses the latter approach, not because I prefer it to the group_by, but just to show you a different way it can be done. (Had engineersmnky used update, I would have gone with group_by.)
Your problem is complicated somewhat by the particular data structure you are using. I found that the solution could be simplfied and made easier to follow by first converting the data to a different structure, update the scores, then convert the result back to your data structure. You may want to consider changing the data structure (if that's an option for you). I've addressed that issue in the "Discussion" section.
Code
def combine_scores(arr)
reconstruct(update_scores(simplify(arr)))
end
def simplify(arr)
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
end
def update_scores(arr)
arr.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
end
def reconstruct(h)
h.map { |k,v| { student: k, scores: v.map { |subject, score|
{ subject: subject, score: score } } } }
end
Example
arr = [
{ student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] },
{ student: "b", scores: [{ subject: "math", quantity: 1 },
{ subject: "english", quantity: 2 } ] },
{ student: "a", scores: [{ subject: "math", quantity: 2 },
{ subject: "science", quantity: 5 } ] }]
combine_scores(arr)
#=> [{ :student=>"a",
# :scores=>[{ :subject=>"math", :score=>12 },
# { :subject=>"english", :score=> 5 },
# { :subject=>"science", :score=> 5 }] },
# { :student=>"b",
# :scores=>[{ :subject=>"math", :score=> 1 },
# { :subject=>"english", :score=> 2 }] }]
Explanation
First consider the two intermediate calculations:
a = simplify(arr)
#=> [{ "a"=>{ "math"=>10, "english"=>5 } },
# { "b"=>{ "math"=> 1, "english"=>2 } },
# { "a"=>{ "math"=> 2, "science"=>5 } }]
h = update_scores(a)
#=> {"a"=>{"math"=>12, "english"=>5, "science"=>5}
# "b"=>{"math"=> 1, "english"=>2}}
Then
reconstruct(h)
returns the result shown above.
+ simplify
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
This maps each hash into a simpler one. For example, the first element of arr:
h = { student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] }
is mapped to:
{ "a"=>Hash[[{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }].map { |g| g.values }] }
#=> { "a"=>Hash[[["math", 10], ["english", 5]]] }
#=> { "a"=>{"math"=>10, "english"=>5}}
Setting the default value of each hash to zero simplifies the update step, which follows.
+ update_scores
For the array of hashes a that is returned by simplify, we compute:
a.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
Each element of a (a hash) is merged into an initially-empty hash, h. As update (same as merge!) is used for the merge, h is modified. If both hashes share the same key (e.g., "math"), the values are summed; else subject=>score is added to h.
Notice that if h_scores does not have the key subject, then:
h_scores[subject] += score
#=> h_scores[subject] = h_scores[subject] + score
#=> h_scores[subject] = 0 + score (because the default value is zero)
#=> h_scores[subject] = score
That is, the key-value pair from g_scores is merely added to h_scores.
I've replaced the block variable representing the subject with a placeholder _, to reduce the chance of errors and to inform the reader that it is not used in the block.
+ reconstruct
The final step is to convert the hash returned by update_scores back to the original data structure, which is straightforward.
Discussion
If you change the data structure, and it meets your requirements, you may wish to consider changing it to that produced by combine_scores:
h = { "a"=>{ math: 10, english: 5 }, "b"=>{ math: 1, english: 2 } }
Then to update the scores with:
g = { "a"=>{ math: 2, science: 5 }, "b"=>{ english: 3 }, "c"=>{ science: 4 } }
you would merely to the following:
h.merge(g) { |_,oh,nh| oh.merge(nh) { |_,ohv,nhv| ohv+nhv } }
#=> { "a"=>{ :math=>12, :english=>5, :science=>5 },
# "b"=>{ :math=> 1, :english=>5 },
# "c"=>{ :science=>4 } }

Using transpose with arrays in Ruby?

I have the following:
table(
[
[
"UnitID",
"First Name",
"Last Name",
"NPS Score",
"Comments",
"emotion",
"polarity"
],
*[
invite_unitid_arr,
invite_name_arr,
invite_lastname_arr,
nps_score_integers,
comment_arr,
SadPanda.emotion(comment_arr),
SadPanda.polarity(comment_arr)
].transpose
]
)
However, SadPanda.emotion(comment_arr) and SadPanda.polarity(comment_arr) return:
undefined method 'gsub!' for #<Array:0x007f8f9b0d40a8>
How can I transpose the array but also use SadPanda.emotion() on each string value within the array?
EDIT:
To be clear, this is what I want as an end result:
[
invite_unitid_arr[0],
invite_name_arr[0],
invite_lastname_arr[0],
nps_score_integers[0],
comment_arr[0],
SadPanda.emotion(comment_arr[0]),
SadPanda.polarity(comment_arr[0])
],
[
invite_unitid_arr[1],
invite_name_arr[1],
invite_lastname_arr[1],
nps_score_integers[1],
comment_arr[1],
SadPanda.emotion(comment_arr[1]),
SadPanda.polarity(comment_arr[1])
]
Etc. etc. The .transpose method does exactly what I need it to do for all values in the array, but I don't know how to pass in comment_arr[0] on the .emotion and .polarity methods and increment that value each time the array is transposed.
To solve the issue:
new_comment_array_emotion = []
new_comment_array_polarity = []
comment_arr.each do |x|
new_comment_array_emotion << SadPanda.emotion(x)
new_comment_array_polarity << SadPanda.polarity(x)
end
table(
[
[
"UnitID",
"First Name",
"Last Name",
"NPS Score",
"Comments",
"emotion",
"polarity"
],
*[
invite_unitid_arr,
invite_name_arr,
invite_lastname_arr,
nps_score_integers,
comment_arr,
new_comment_array_emotion,
new_comment_array_polarity
].transpose
]
)
Feel free to propose a cleaner way of doing this within Ruby.

Resources