descending sorting on hash values - ruby

Say, I have a hash which has elements like:
###EVNT-66 => 8.40,
###EVNT-108 => 9.11,
###EVNT-345 => 88.22,
###EVNT-143 => 1.26
I wanted to sort them in descending order based on the hash values. So, I wrote this:
h.sort_by {|_key, value| value}.reverse
Surprisingly enough, I got results in this fashion:
###EVNT-108 => 9.11,
###EVNT-66 => 88.22,
###EVNT-345 => 8.40,
###EVNT-143 => 1.26
which is wrong. Line with '88' in it should be at the top. I am not sure what else could I write to achieve this.

it seems sorted alphabetically. convert to numeric first.
h.sort_by {|_key, value| -value.to_f}
the minus sign could avoid the using of reverse

Related

Printing Values in a Hash using the conditional operator "for" and Ruby's .each

I am attempting to solve what should be a very simple problem in Ruby. My goal is to take the following hash and print values and keys using for loops and the .each method.
my_stocks = { 'Apple' => { 'symbol' => 'AAPL', 'price' => 100 },
'Google' => { 'symbol' => 'GOOG', 'price' => 1150 },
'Tesla' => { 'symbol' => 'TSLA', 'price' => 295 },
'Microsoft' => { 'symbol' => 'MSFT', 'price' => 95},
'Netflix' => { 'symbol' => 'NFLX', 'price' => 300},
'Facebook' => { 'symbol' => 'FB', 'price' => 175},
'Amazon' => { 'symbol' => 'AMZN', 'price' => 1250} }
So, I'm using this defined method to print the entire hash:
def execute_exercise(my_stocks)
for i, j in my_stocks
puts j
end
end
Obviously, this succeeds. What I am trying to do next is, use a for loop to print only the values of keys that exceed 500.
Every solution I've come up with raises an error in Ruby:
rbtest.rb:18:in `>': no implicit conversion of Fixnum into Hash (TypeError)
from rbtest.rb:18:in `block in execute_exercise'
from rbtest.rb:18:in `each'
from rbtest.rb:18:in `execute_exercise'
from rbtest.rb:25:in `<main>'
The attempts I've made are:
my_stocks.each {|symbol,price| puts symbol if price > 500}
for q, e in my_stocks.to_i < 500
puts e
end
I expect the values to print from the hash, but it's obvious that I'm missing something here. I can't convert the fixnum into hash without .to_h, but does that even work? Am I just misunderstanding for loops or is it something else?
You're asking Ruby to do way too much at once here:
for q, e in my_stocks.to_i < 500
That really doesn't make any sense. for iterates over a container. to_i returns an integer. x.to_i < n returns a boolean. You can't iterate over a boolean. Plus, my_stocks is a Hash and it doesn't do to_i, so this whole line is not something Ruby can make sense of.
Instead think about the problem differently. The Ruby way is to break this down into steps. First, find all stocks over a particular value:
my_stocks.select do |_name, data|
data['price'] > 500
end.each do |name, data|
# ... print or whatever
end
Where this taps into Enumerable to help solve the problem in two stages. select to filter, each to iterate. In Ruby for isn't really used, it's really never the best tool for the job.
The real power of Enumerable is that you can chain one operation right into the next. end.each might look very odd to someone unfamiliar with Ruby, but that's really how Ruby handles complex operations with ease.
For example, to filter and sort by price (highest to lowest) is only a small modification:
my_stocks.select do |_name, data|
data['price'] > 500
end.sort_by do |_name, data|
-data['price']
end.each do |name, data|
# ... print or whatever
end
The -data['price'] part is an easy way of avoiding having to reverse the values later. Normally it sorts in increasing order, so inverting sorts in decreasing as the highest value becomes the lowest.
You can use the loop syntax like this but just use "next" to skip iteration on item who's price doesn't meet your requirement:
for q, e in my_stocks
next unless e['price'] > 500
puts q
puts e
end
This will output:
Google
{"symbol"=>"GOOG", "price"=>1150}
Amazon
{"symbol"=>"AMZN", "price"=>1250}

Sort the hash having the key as "YYYY-MM" in rails

I have an hash having the keys in the format "YYYY-MM".
I want to sort the hash in the ascending order . Below is what I'd like:
{"2013-05" => 2 , "2013-06" => 4 , "2013-07" =>10 , ... }
require 'Date'
my_hash = {"2013-10" => 2 , "2013-06" => 4 , "2013-07" =>10}
new_hash = Hash[my_hash.sort_by{|k,_| Date.strptime(k, '%Y-%m')}]
p new_hash # => {"2013-06"=>4, "2013-07"=>10, "2013-10"=>2}
Just remove then Hash[] part if you rather want the output to be an array. You'll need to do this if using Ruby <= 1.8.7 (in older versions of Ruby, hashes have no defined order).
Edit: There are two reasons why sorting by parsing a date is preferable over just sorting the strings. The first one is validation, To check that the date-strings in the hash are actually in the correct format. The second in that it will also handle, and sort correctly, months without and a zero added. "2013-06" and "2013-6" will be treated the same, and that is not the case if you only sort using the string.
Sort by key also works:
h = {"2013-10" => 2 , "2013-06" => 4 , "2012-07" =>10}
Hash[h.sort_by{|k,_| k}]
# => {"2012-07"=>10, "2013-06"=>4, "2013-10"=>2}
#which is same as h.sort
Hash[h.sort]
# => {"2012-07"=>10, "2013-06"=>4, "2013-10"=>2}
The simplest way:
> h={"2013-10" => 2 , "2013-06" => 4 , "2013-07" =>10 }
=> {"2013-10"=>2, "2013-06"=>4, "2013-07"=>10}
> Hash[h.sort]
=> {"2013-06"=>4, "2013-07"=>10, "2013-10"=>2}

How do you define element uniqueness by multiple keys/attributes?

I have queried my database which gave me an array of hashes, where the keys in the hash are the column names. I want to keep only the hashes(array elements), that are unique according to multiple (3 columns). I have tried:
array.uniq { |item| item[:col1], item[:col2], item[:col3] }
as well as
array = array.inject([{}]) do |res, item|
if !res.any? { |h| h[:col1] == item[:col1] &&
h[:col2] == item[:col2] &&
h[:col3] == item[:col3] }
res << item
end
end
Does anyone have any ideas as to what's wrong or another way of going about this?
Thanks
It's unclear to me what you're asking for. My best guess is that given the array of single-association Hashes:
array = [{:col1 => 'aaa'}, {:col2 => 'bbb'}, {:col3 => 'aaa'}]
You'd like to have only one Hash per hash value; that is, remove the last Hash because both it and the first one have 'aaa' as their value. If so, then this:
array.uniq{|item| item.values.first}
# => [{:col1=>"aaa"}, {:col2=>"bbb"}]
Does what you want.
The other possibility I'm imagining is that given an array like this:
array2 = [{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'x'},
{:col1 => 'd', :col2 => 'b', :col3 => 'c', :col4 => 'y'},
{:col1 => 'a', :col2 => 'b', :col3 => 'c', :col4 => 'z'}]
You'd like to exclude the last Hash for having the same values for :col1, :col2, and :col3 as the first Hash. If so, then this:
array2.uniq{|item| [item[:col1], item[:col2], item[:col3]]}
# => [{:col1=>"a", :col2=>"b", :col3=>"c", :col4=>"x"},
# {:col1=>"d", :col2=>"b", :col3=>"c", :col4=>"y"}]
Does what you want.
If neither of those guesses are really want you're looking for, you'll need to clarify what you're asking for, preferably including some sample input and desired output.
I'll also point out that it's quite possible that you can accomplish what you want at the database query level, depending on many factors not presented.
If the no. of column is constant i.e. 3 you are better off creating a 3 level hash something like below
where whatever value you want to store is at 3rd level.
out_hash = Hash.new
array.each do |value|
if value[:col1].nil?
out_hash[value[:col1]] = Hash.new
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2].nil?
out_hash[value[:col1]][value[:col2]] = Hash.new
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
else if value[:col1][:col2][:col3].nil?
out_hash[value[:col1]][value[:col2]][value[:col3]] = value
end
end
I have not tested the code above its for giving you a idea...

How to get first n elements from Hash in ruby?

I have a Hash and i have sorted it using the values
#friends_comment_count.sort_by{|k,v| -v}
Now i only want to get hash of top five elements .. One way is to use a counter and break when its 5.
What is preferred way to do in ruby ?
Thanks
h = { 'a' => 10, 'b' => 20, 'c' => 30 }
# get the first two
p Hash[*h.sort_by { |k,v| -v }[0..1].flatten]
EDITED:
# get the first two (more concisely)
p Hash[h.sort_by { |k,v| -v }[0..1]]
Can't you just do something like:
h = {"test"=>"1", "test2"=>"2", "test3"=>"3"}
Then if you wanted the first 2:
p h.first(2).to_h
Result:
=> {"test"=>"1", "test2"=>"2"}
New to ruby myself (please be nice if I'm wrong guys!) but does this work?
#friends_comment_count.sort_by{|k,v| -v}.first 5
Works for me in IRB, if I've understood what you're trying to achieve correctly
You can't sort a Hash and that's why sort_by does NOT sort your Hash. It returns a sorted Array of Arrays.
In Ruby 2.2.0 and later, Enumerable#max_by takes an optional integer argument that makes it return an array instead of just one element. This means you can do:
h = { 'a' => 10, 'b' => 20, 'c' => 30 }
n = 2
p h.max_by(n, &:last).to_h # => {"b"=>20, "c"=>30}
Hashes are not ordered by nature (even thought in Ruby implementation they are). Try geting converting your Hash to Array and get [0,4] out of it

Iterate hash for specific range

How to pass range in hash to iterate from index 1 to last?
h = {}
h[1..-1].each_pair do |key,value|
puts "#{key} = #{value}
end
This code returning error. how may i pass range in hash ??
EDIT:
I want to print first key and value without any calculations.
From second key and value i want to do some calculation on my hash.
For that i have written this code ...
store_content_in_hash containing key and values.
first_key_value = store_content_in_hash.shift
f.puts first_key_value[1]
f.puts
store_content_in_hash.each_pair do |key,value|
store_content_in_hash[key].sort.each {|v| f.puts v }
f.puts
end
Any better way to solve out this problem ??
In Ruby 1.9 only:
Given a hash:
h = { :a => :b, :c => :d, :e => :f }
Go Like this:
Hash[Array(h)[1..-1]].each_pair do |key, value|
# ...
end
This will iterate through the following hash { :c => :d, :e => f } as the first key/value pair is excluded by the range.
Hashes have no concept of order. There is no such thing as the first or second element in a hash.
So you can't do what you want with hashes.
Hash is not about the ranges. It's about key value pairs. In ruby 1.8 hash is unordered hence you can't be sure in which order the keys and values will be iterated over which makes "range" thing obsolete. And I believe that you're doing something wrong (tm) in this situation. Can you elaborate on your problem?
On the other note you're getting an error because square brackets in Hash instance accepts keys. So if your hash does not contain 1..-1 as a key - you will get nil value and nil does not respond to each_pair. Try this to get a hold on this:
h = {(1..-1) => {:foo => :bar}}
h[1..-1].each_pair do |key,value|
puts "#{key} = #{value}"
end
As others have pointed out, Hashes are not about order. It's true that 1.9 hashes are ordered, but that's just a convenience, not their primary feature.
If the order is important to you, just use arrays instead of hashes. In your case, an array of pairs (two-element arrays) seems to fit the purpose. And if you need to access it by key, you can always easily convert it to a hash using Hash#[] method.

Resources