I have this hash where the keys are 0, 3, and 5 and the values are hashes.
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}}
How can I implement something like this:
h.map { |key|
if key[:occurrences] > 2
key[:occurrences] += 1
end
}
I know this syntax doesn't work. I want to increment the occurrence value when a condition is met and I am not sure how to access the key of a key but I would like the result to be:
{0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"},
5=>{:occurrences=>4, :className=>"nah"}}
To update the existing hash you can simply call each_value. It passes each value in your hash to the block and within the block you can update the value (based on a condition):
h = {
0=>{:occurrences=>1, :className=>"class"},
3=>{:occurrences=>3, :className=>"hello"},
5=>{:occurrences=>3, :className=>"nah"}
}
h.each_value { |v| v[:occurrences] += 1 if v[:occurrences] > 2 }
#=> {
# 0=>{:occurrences=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}
# }
Just out of curiosity:
input.map do |k, v|
[k, v[:occurrences].to_i > 2 ? v.merge(occurrences: v[:occurrences] + 1) : v]
end.to_h
#⇒ {0=>{:occurrence=>1, :className=>"class"},
# 3=>{:occurrences=>4, :className=>"hello"},
# 5=>{:occurrences=>4, :className=>"nah"}}
h = { 0=>{ :occurrences=>1, :className=>"class" },
3=>{ :occurrences=>3, :className=>"hello" },
5=>{ :occurrences=>3, :className=>"nah" } }
f = h.dup
Non-destructive case
h.transform_values do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == f
#=> true
Destructive case
g = h.transform_values! do |g|
g[:occurrences] > 2 ? g.merge(occurrences: g[:occurrences] + 1) : g
end
#=> { 0=>{ :occurrences=>1, :className=>"class" },
# 3=>{ :occurrences=>4, :className=>"hello" },
# 5=>{ :occurrences=>4, :className=>"nah" } }
h == g
See Hash#transform_values and Hash#transform_values!, which made their debut in MRI v.2.4. Note that, in the destructive case, merge! is not needed.
For example I would want the entire hash returned but with updated
values: {0=>{:occurrence=>1, :className=>"class"},
3=>{:occurrences=>4, :className=>"hello"}, 5=>{:occurrences=>4,
:className=>"nah"}}. If the value of the occurrences key is greater
than two than I want to increment that value and still have the entire
hash.
Here you go:
h = {0=>{:occurrences=>1, :className=>"class"}, 3=>{:occurrences=>3, :className=>"hello"}, 5=>{:occurrences=>3, :className=>"nah"}}
new_h = h.map do |k, v|
if v[:occurrences] > 2
v[:occurrences] += 1
end
[k, v]
end.to_h
Related
Hello I am wondering if anyone may be able to give some assistance with two functions I am working on for a Ruby project. I have an array of Objects, and I need to get a certain attribute with the highest and lowest occurrence in each function respectively.
So far I have this, which works, but seems a bit too verbose:
def most_visited_port(time)
most_visited_port_name = ""
most_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
most_visited = ending_port_array.sort.max_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.max_by { |v| ending_port_array.count(v) } ? false : ending_port_array.max_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == most_visited
most_visited_port_name = ship.ending_port_name
end
end
pp most_visited_port_name
end
def least_visited_port(time)
least_visited_port_name = ""
least_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
least_visited = ending_port_array.sort.min_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.min_by { |v| ending_port_array.count(v) } ? false : ending_port_array.min_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == least_visited
least_visited_port_name = ship.ending_port_name
end
end
pp least_visited_port_name
end
Here is a sample of the array of Objects format:
[#<FleetShip:0x0000000108444450
#average_speed=46.02272727272727,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=0,
#ship_name="Alpha",
#time_arrived=2016-06-12 08:05:36 -0500,
#time_left=2016-06-12 06:20:00 -0500>,
#<FleetShip:0x0000000108444400
#average_speed=32.01932579334578,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=1,
#ship_name="Sea Ghost",
#time_arrived=2016-06-12 11:07:47 -0500,
#time_left=2016-06-12 08:36:00 -0500>]
But could anyone give some assistance on a possibly simpler or more concise way to pull it off?
For fun, you could use each_with_object to build a hash of ending_port values and their frequency, and then retrieve the most frequent.
I'm going to use a much simpler example.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1), A.new(3), A.new(3)]
most_freq_b = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}.max_by(&:last).first
# => 3
This does not account for situations where more than one value for b occurs equal numbers of times. We can tweak it though, to accomplish this.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1)]
freq = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}
highest_freq = freq.values.max
most_freq_b = freq.select { |_, v| v == highest_freq }.keys
# => [3, 1]
Alternatively, we can provide a default value of 0 for the hash, simplifying part of the code.
freq = c.each_with_object(Hash.new(0)) { |x, h|
h[x.b] += 1
}
I have a array like
array = [
{"point"=>6, "score"=>4, "team"=>"Challenger"},
{"point"=>4, "score"=>2, "team"=>"INB"},
{"point"=>2, "score"=>2, "team"=>"Super-11"},
{"point"=>3, "score"=>7, "team"=>"INB"}
]
I want to merge hashes by "team" and sum the values of "point" and "score". Additionally want to insert an key "qualified" in each hash if point is greater than 5. So the final result will be:
result= [
{"point"=>6, "score"=>4, "qualified"=> "yes", "team"=>"Challenger"},
{"point"=>7, "score"=>9, "qualified"=> "yes", "team"=>"INB"},
{"point"=>2, "score"=>2, "qualified"=> "no", "team"=>"Super-11"}
]
Any help would be appreciated. Thanks!
One more possible solution :)
array.group_by { |item| item['team'] }.map do |_, items|
result = items.inject({}) { |hash, item| hash.merge(item) { |_, old, new| Integer(old) + new rescue old } }
result.merge("qualified" => result['point'] > 5 ? "yes" : "no")
end
Combination of group_by and map should help
result =
array.group_by {|item| item['team'] }
.map do |team, items|
total_points = items.map{|item| item['point']}.reduce(0, :+)
total_score = items.map{|item| item['score']}.reduce(0, :+)
qualified = points > 5
{
'point' => total_points,
'score' => total_score,
'qualified' => qualified ,
'team' => team
}
end
result = array.group_by{|i| i['team']}
.map do |k,v|
points = v.map{|i| i['point']}.inject(0, :+)
score = v.map{|i| i['score']}.inject(0, :+)
{
'point' => points,
'score' => score,
'qualified' => points > 5 ? 'yes' : 'no',
'team' => k
}
end
This is an alternative version. group_by is mandatory, I guess.
I used a temporary hash with keys as symbol to store data during iterations.
result = array.group_by { |hash| hash['team'] }.map do |team|
tmp_hash = {point: 0, score: 0, team: team[0], qualified: 'no'}
team[1].each { |h| tmp_hash[:point] += h['point'] ; tmp_hash[:score] += h['score'] }
tmp_hash[:qualified] = 'yes' if tmp_hash[:point] > 5
tmp_hash
end
this gives as result:
# => [
# {:point=>6, :score=>4, :team=>"Challenger", :qualified=>"yes"},
# {:point=>7, :score=>9, :team=>"INB", :qualified=>"yes"},
# {:point=>2, :score=>2, :team=>"Super-11", :qualified=>"no"}
# ]
After doing group_by, a simple map operation which takes the first element as the mapped value, sums up point and score within it and then merges the qualified condition into it is easy enough:
array
.group_by { |h| h["team"] }
.map do |_, a|
["point", "score"].each { |k| a.first[k] = a.sum { |h| h[k] } }
a.first.merge({"qualified": a.first["score"] > 5 ? 'yes' : 'no'})
end
Online demo here
array.each_with_object({}) do |g,h|
h.update(g["team"]=>g.merge("qualified"=>g["score"] > 5 ? "yes" : "no")) do |_,o,n|
{ "point" =>o["point"]+n["point"],
"score" =>o["score"]+n["score"],
"team" =>o["team"],
"qualified"=>(o["score"]+n["score"]) > 5 ? "yes" : "no" }
end
end.values
#=> [{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
# {"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
# {"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}]
This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys (here the value of :id) that are present in both hashes being merged. See the doc for the description of the three block variables (here _, o and n).
Note that the receiver of values (at the end) is
{"Challenger"=>{"point"=>6, "score"=>4, "team"=>"Challenger", "qualified"=>"no"},
"INB"=>{"point"=>7, "score"=>9, "team"=>"INB", "qualified"=>"yes"},
"Super-11"=>{"point"=>2, "score"=>2, "team"=>"Super-11", "qualified"=>"no"}}
One could alternatively make a separate pass at the end to add the key "qualified':
array.each_with_object({}) do |g,h|
h.update(g["team"]=>g) do |_,o,n|
{ "point" =>o["point"]+n["point"],
"score" =>o["score"]+n["score"],
"team" =>o["team"] }
end
end.values.
map { |h| h.merge("qualified"=>(h["score"] > 5) ? "yes" : "no") }
My hash example:
{"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
I need to get parameters for which the key is 1:
["85_6", "86_6", "82_8638", "83_8638", "85_8638", "81_8640", "82_8640", "83_8640"]
Next, i need to group:
{"6"=>"85, 86", "8638"=> "83, 82, 85", "8640" => "81, 82, 83"}
hash.select { |_, v| v == '1' }
.keys
.map { |e| e.split('_') }
.group_by(&:pop)
.map { |k, v| [k, v.join(', ')] }
.to_h
#⇒ {
# "6" => "85, 86",
# "8638" => "82, 83, 85",
# "8640" => "81, 82, 83"
# }
One more solution (with just 1 iteration):
h.each_with_object(Hash.new {|h, k| h[k] = ''}) do |(k, v), m|
f, s = k.split('_')
m[s] << (m[s].empty? ? f : ", #{f}") if v == '1'
end
#=> {"6"=>"85, 86", "8638"=>"82, 83, 85", "8640"=>"81, 82, 83"}
yes, i know this is a bucket of crap, but posting it here because of the wasted 15 mins -))
a = {"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
a.select {|k| a[k] == '1' }
.keys.map {|e| e.split('_')}
.map(&:reverse)
.group_by(&:first)
.map{|k,v| [k, v.flatten.join(",")] }
.gsub("#{k},", " ")] }.to_h
hash = {"79_6"=>"0", "85_6"=>"1", "86_6"=>"1", "79_8638"=>"0", "80_8638"=>"0", "81_8638"=>"0", "82_8638"=>"1", "83_8638"=>"1", "84_8638"=>"0", "85_8638"=>"1", "86_8638"=>"0", "79_8639"=>"0", "80_8639"=>"0", "81_8639"=>"0", "82_8639"=>"0", "83_8639"=>"0", "84_8639"=>"0", "85_8639"=>"0", "86_8639"=>"0", "80_8640"=>"0", "81_8640"=>"1", "82_8640"=>"1", "83_8640"=>"1", "84_8640"=>"0", "85_8640"=>"0", "86_8640"=>"0"}
new_hash = hash.keep_if {|k,v| v == "1"} # keep only value == "1"
.map{|a|a.first.gsub("_",",").split(",")} # clean data
.group_by(&:pop) # group data by the last value which become the key
new_hash.map{|k,v| [k, v.join(',')]}.to_h #transform array of values in string and array to hash
Hope it's help, don't be to harsh it's my first answer on StackOver ;)
I'd like to delete empty hashes at different nested levels. And once that empty hash is deleted, I'd like to delete it's container hash as well. How would I do this?
Here is the hash I want to work on:
{
"query"=>{"filtered"=>{
"query"=>{"bool"=>{}},
"filter"=>{"query"=>{"query_string"=>{
"fields"=>[["standard_analyzed_name", "standard_analyzed_message"]],
"query"=>"Arnold AND Schwarz"
}}}
}},
"sort"=>[{"total_interactions"=>{"order"=>"desc"}}]
}
Below is the code that I have that does not remove the empty {"query"=>{"bool"=>{}} part:
def compactify_hash(main_hash)
main_hash.each do |key, value|
if(value.class == Hash && !value.empty?)
compactify_hash(value)
elsif(value.class == Hash && value.empty?)
main_hash.delete(key)
end
end
return main_hash
end
You have a few problems here:
You're modifying the hash in-place when you might not mean to, you'd probably want to name your method compactify_hash! if you really want to modify it in-place.
You have Arrays inside your Hash but you don't scan them for empty Hashes.
Most importantly, you never check your recursively compactified hashes to see if compactifying them has also emptied them. in here:
if(value.class == Hash && !value.empty?)
compactify_hash(value)
elsif(value.class == Hash && value.empty?)
main_hash.delete(key)
end
you need to check value.empty? after you compactify_hash(value).
You could do something like this instead:
def compactify_hash(main_hash)
main_hash.each_with_object({}) do |(k, v), h|
if(v.is_a?(Hash))
ch = compactify_hash(v)
h[k] = ch if(!ch.empty?)
elsif(v.is_a?(Array))
h[k] = v.map do |e|
e.is_a?(Hash) ? compactify_hash(e) : e
end
else
h[k] = v
end
end
end
I have assumed that for any hash h, you want to create another hash g which is the same as h except no nested hash in g will have a key-value pair [k,v] for which v.respond_to(:empty?) and v.empty? both return true. For example, if the following nested hash is present in h:
{ a: { b: '', c: {}, d: [] }, e: 3 }
then the corresponding nested hash in g will be:
{ e: 3 }
In effect, we "remove" the key-value pairs :b=> '', :c=> {} and :d=> [] because '', {} and [] all respond to :empty? and all are empty. That reduces the nested hash to:
{ a: {}, e: 3 }
Since the value of a is now empty, we remove that key-value pair, leaving the nested hash in g equal to:
{ e: 3 }
I believe this can be achieved as follows:
def remove(h)
loop do
h_new = remove_empties(h)
break h if h==h_new
h = h_new
end
end
def remove_empties(h)
h.each_with_object({}) do |(k,v),g|
case v
when Hash
g[k] = remove_empties(h[k]) unless v.empty?
else
g[k] = v unless v.respond_to?(:empty?) && v.empty?
end
end
end
For your hash, which I refer to as h:
remove(h)
#=> {:query=>
# {:filtered=>
# {:filter=>
# {:query=>
# {:query_string=>
# {:fields=>
[["standard_analyzed_name", "standard_analyzed_message"]],
# :query=>"Arnold AND Schwarz"
# }
# }
# }
# }
# },
# :sort=>[
# {:total_interactions=>
# {:order=>"desc"}
# }
# ]
# }
Note that the recursion is performed repeatedly until no further modifications of the hash are performed.
I have a hash which looks like this
#hash = {
0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil], 2=>[nil], 3=>[nil], 4=>[nil], 5=>[nil], 6=>[nil], 7=>[nil], 8=>[nil],
9=>[nil], 10=>[nil], 11=>[nil], 12=>[nil], 13=>[nil], 14=>[nil], 15=>[nil],
16=>[nil], 17=>[nil], 18=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]
}
I want the highest value from this hash in a variable. The output should be
#h = 7.9 \\only float value which should be highest among all
so I am doing like this
#hash.each do |k, v|
if !v.nil?
#h= [v.flatten.sort{ |v1, v2| v2['value'] <=> v1['value'] }.first['value']]
end
end
but sometimes it works, and most of the times it doesn't.
#hash.values.flatten.compact.map { |h| h["value"] }.max
=> 7.9
Which equates to:
Get the values of the hash as an array
Flatten all the elements in the values array
Compact to remove all nil entries
Map the remaining entries to the ["value"] element in the hash
Return the maximum of all those value
It makes a lot of assumptions about the format of your #hash though.
I prefer #Shadwell's solution, but here's another way:
hash.select { |_,v| v.first }
.max_by { |_,v| v.first["value"] }
.last
.first["value"]
#=> 7.9
The steps (with all but one n=>[nil] element removed for readabiity):
hash = { 0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]}
h = hash.select { |_,v| v.first }
#=> { 0=>[{"name"=>"guest", "value"=>7.9}],
# 19=>[{"name"=>"test", "value"=>2.5}],
# 20=>[{"name"=>"roam", "value"=>2.5}],
# 21=>[{"name"=>"test2", "value"=>1.58}],
# 22=>[{"name"=>"dff", "value"=>1.9}],
# 23=>[{"name"=>"dddd", "value"=>3.16}]}
a = h.max_by { |_,v| v.first["value"] }
#=> [0, [{"name"=>"guest", "value"=>7.9}]]
b = a.last
#=> [{"name"=>"guest", "value"=>7.9}]
b.first["value"]
#=> 7.9