reversing hash.sort_by in ruby - ruby-on-rails

Simple enough but Im drawing a blank on it.
#sortedHash = #otherHash.sort_by { |k,v| v }
This stores the has based on key value in ascending order. in other words first value in hash is lowest. How do i reverse (descending order) it so that the highest value is at the top?
#sortedInternalLinksHash = #countHash.sort_by { |k,v| -v }
#sortedInternalLinksHash = #countHash.sort_by { |k,v| !v }

#sortedInternalLinksHash = #countHash.sort_by { |k,v| v }.reverse

Also you can use:
#sortedInternalLinksHash = #countHash.sort_by { |_,v| - v }

Related

add contents of 1 hash into another

I have a parent hash which changes and I want to ensure that the child hashes take these changes but also retain keys that they had before and those should not be lost
These are the sample hashes that I have
one = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd"]}}
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "suez", "kiel"]}}
I want the other hash to now look like
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd", suez", "kiel"]}}
I have tried the following code but it is not working
result = propogate_changes(one, other)
def propogate_changes(one, other)
one_keys = one.keys
other_keys = other.keys
combined = Hash.new
unique_keys = one_keys.concat(other_keys).uniq
unique_keys.each do |key|
if(one[key].is_a?(Array)) then
# if(other[key] == nil) then
# combined[key] = one[key]
# else
combined[key] = one[key].concat(other[key]).uniq
# end
else
combined[key] = add_allowance(one[key], other[key])
end
end
return combined
end
The above code fails when a key is present in one but missing in another
I also tried merge, deep_merge, reverse_merge but they all overwrite my other hash with one hash but none of them retain the original data.
Any advise on this will be appreciated
Try this custom merge logic.
def find_missing_items_in_arr(arr1, arr2)
arr1_size = arr1.size
arr2_size = arr2.size
if (arr1_size == arr2_size) && (arr1 & arr2).size == arr1_size
return [] # Same array
end
arr2 - arr1
end
def custom_merge(target_hash, source_hash)
# If you want to preserve frozen state of entries, please use `clone`
duped_target_hash = target_hash.dup
source_hash.each do |k, v|
unless duped_target_hash.key?(k)
duped_target_hash[k] = v
next
end
case v
when Array
missing_items_in_arr = find_missing_items_in_arr(duped_target_hash[k], v)
if missing_items_in_arr.size > 0
duped_target_hash[k] += missing_items_in_arr
end
when Hash
duped_target_hash[k] = custom_merge(duped_target_hash[k], v)
else
# Nothing to do here
end
end
duped_target_hash
end
Usage
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"]
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"]
}
}
rs_hash = custom_merge(other, one)
puts rs_hash
Note: Rails provides a deep_merge but this can be used outside Rails. I have tested and it returns your desired output. Also it handles more nested entries like
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"],
"custom_options" => {
"custom_output" => ["abc"],
"custom_input" => ["xyz" ]
}
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"],
"custom_options" => {
"custom_output" => ["abc", "def"]
}
}
}
Hope this helps.

Ruby calculate the percentage of elements in hash

I have a hash :
hash = {"str1"=>2, "str2"=>3, "str3"=>7}
I want to calculate the percentage of each element in the hash so I can get one like this :
{"str1"=>16.66% , "str2"=>25.00%, "str3"=>58.33%}
Any idea about that? Thanks
You can use Enumerable#each_with_object:
sum = a.values.inject(0, :+) # or simply a.values.sum if you're on Ruby 2.4+
#=> 12
a.each_with_object({}) { |(k, v), hash| hash[k] = v * 100.0 / sum }
#=> {"str1"=>16.666666666666668, "str2"=>25.0, "str3"=>58.333333333333336}
To have it with %:
a.each_with_object({}) { |(k, v), hash| hash[k] = "#{(v * 100.0 / sum).round(2)}%" }
#=> {"str1"=>"16.67%", "str2"=>"25.0%", "str3"=>"58.33%"}
The best answer IMHO was unfortunately deleted:
total = hash.values.sum
hash.transform_values { |v| (v * 100.0 / total).round(2) }
The hash method transform_values is relatively unknown and this case is exactly what it is for. (Ruby 2.4+ or Rails 4.2+)
#Ursus, if you undelete yours I'll delete this. Keep in mind that answers here are not just for OP but anyone else who has the same question in the future.
Thanks to #AndreyDeineko for his quick answer!
I tried to do it with an each method only (instead of each_with_object, so here is the answer.
sum = hash.values.sum
result = {}
hash.each { |k,v| result[k] = (v*100.00/sum).round(2)}
To have it with %:
hash.each { |k,v| result[k] = (v*100.00/sum).round(2).to_s + "%"}

How to sort Ruby Hash based on date?

I have a hash object with the following structure:
{"action1"=>
{"2014-08-20"=>0,
"2014-07-26"=>1,
"2014-07-31"=>1
},
"action2"=>
{"2014-08-01"=>2,
"2014-08-20"=>2,
"2014-07-25"=>2,
"2014-08-06"=>1,
"2014-08-21"=>1
}
"action3"=>
{"2014-07-30"=>2,
"2014-07-31"=>1,
"2014-07-22"=>1,
}
}
I want to sort the hash based on the date and return back a Hash(Not array). The final result should be:
{"action1"=>
{"2014-07-26"=>1,
"2014-07-31"=>1,
"2014-08-20"=>0
},
"action2"=>
{"2014-07-25"=>2,
"2014-08-01"=>2,
"2014-08-06"=>2,
"2014-08-20"=>1,
"2014-08-21"=>1
}
"action3"=>
{"2014-07-22"=>1,
"2014-07-30"=>2,
"2014-07-31"=>1
}
}
Iterate over the hash, and for each value, sort.
h = {"action1"=>
{"2014-08-20"=>0,
"2014-07-26"=>1,
"2014-07-31"=>1
},
"action2"=>
{"2014-08-01"=>2,
"2014-08-20"=>2,
"2014-07-25"=>2,
"2014-08-06"=>1,
"2014-08-21"=>1
},
"action3"=>
{"2014-07-30"=>2,
"2014-07-31"=>1,
"2014-07-22"=>1,
}
}
h.each do |k, v|
h[k] = Hash[v.sort]
end
Here you need to iterate your hash and fetch the value than you need to apply sort_by function on each value so you will get your result
hashName.each do |key, hash|
Hash[hashName.sort_by{|k,v| k}]
end
This is all you need:
h.each { |k,v| h[k] = v.sort.to_h }
#=> {"action1"=>{"2014-07-26"=>1, "2014-07-31"=>1, "2014-08-20"=>0},
# "action2"=>{"2014-07-25"=>2, "2014-08-01"=>2, "2014-08-06"=>1,
# "2014-08-20"=>2, "2014-08-21"=>1},
# "action3"=>{"2014-07-22"=>1, "2014-07-30"=>2, "2014-07-31"=>1}}
Hash#to_h appeared in Ruby 2.0. For earlier versions, use the class method Hash::[]: i.e., replace v.sort.to_h with Hash[v.sort].

How do I convert an array of hashes into a sorted hash?

If I have an array of hashes, each with a day key:
[
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
What is the best way to convert it to a hash with sorted day values as the keys:
{
:1=>[{:day=>1,:name=>'Ben'}],
:4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
I'm using Ruby 1.9.2 and Rails 3.1.1
Personally, I wouldn't bother "sorting" the keys (which amounts to ordering-by-entry-time in Ruby 1.9) until I actually needed to. Then you can use group_by:
arr = [{:day=>4,:name=>'Jay'}, {:day=>1,:name=>'Ben'}, {:day=>4,:name=>'Jill'}]
arr.group_by { |a| a[:day] }
=> {4=>[{:day=>4, :name=>"Jay"}, {:day=>4, :name=>"Jill"}],
1=>[{:day=>1, :name=>"Ben"}]}
Instead, sort the keys when you actually need them.
Assuming you array is called is list, here's one way using the reduce method:
list.reduce({}) { |hash, item|
(hash[item[:day]] ||= []) << item; hash
}
Here's another using the map method, but you have to carry a holder variable around:
hash = {}
list.each { |item|
(hash[item[:day]] ||= []) << item
}
Once you have the unsorted hash say in variable foo, you can sort it as,
Hash[foo.sort]
Simple answer:
data = [
{:day=>4,:name=>'Jay'},
{:day=>1,:name=>'Ben'},
{:day=>4,:name=>'Jill'}
]
#expected solution
sol = {
1=>[{:day=>1,:name=>'Ben'}],
4=>[{:day=>4,:name=>'Jay'},{:day=>4,:name=>'Jill'}]
}
res = {}
data.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
Problem with this solution: The hash is not sorted as requested. (Same problem has Anurags solution).
So you must modify the answer a bit:
res = {}
data.sort_by{|h| h[:day]}.each{|h|
res[h[:day]] ||= []
res[h[:day]] << h
}
p res
p res == sol #check value
p res.keys == sol.keys #check order
In Rails you can use OrderedHash:
ActiveSupport::OrderedHash[arr.group_by { |a| a[:day] }.sort_by(&:first)]
Update: In fact in Ruby 1.9 hash is ordered, so using ActiveSupport extension is not required:
Hash[arr.group_by { |a| a[:day] }.sort_by(&:first)]

What is an elegant way to replace an element of an array based on a match criteria?

I am using the following logic to update a list item based on a criteria.
def update_orders_list(order)
#orders.delete_if{|o| o.id == order.id}
#orders << order
end
Ideally, I would have preferred these approaches:
array.find_and_replace(obj) { |o| conditon }
OR
idx = array.find_index_of { |o| condition }
array[idx] = obj
Is there a better way?
array.map { |o| if condition(o) then obj else o }
maybe?
As of 1.8.7, Array#index accepts a block. So your last example should work just fine with a minor tweak.
idx = array.index { |o| condition }
array[idx] = obj

Resources