1

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

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
media
  • 135
  • 1
  • 7
  • Use [`Hash#transform_values`](http://ruby-doc.org/core-2.4.1/Hash.html#method-i-transform_values) to do this. – Sagar Pandya May 09 '17 at 16:07
  • Welcome to Stack Overflow. Please read "[ask]" and "[mcve]" and the linked pages and "[How much research effort is expected of Stack Overflow users?](http://meta.stackoverflow.com/questions/261592)". Your question isn't asked well. Did you search? If not why? If so, where and why didn't it help? Did you write code? If not, why? If so, what is the minimum code necessary to demonstrate the problem you're asking about? – the Tin Man May 09 '17 at 19:48
  • @theTinMan thank you. Yes I searched and didn't find what I was looking for tha'ts why I asked. I think my question is clear enough with the example I gave. I got the answer I needed in just few minutes after asking. I don't know why the negative vote is for though ... – media May 10 '17 at 09:12
  • Hover over the arrows to see what they mean. If you searched you should say where and explain why it didn't help. That helps us avoid duplicating your effort to help you and helps others find additional sources. Getting the answer in a few minutes means you should wait to see if a better answer appears. It takes about 24 hours for all the community to see a question and formulate answers. Remember, SO isn't here to solve your problem only, it's here to document solutions for everyone in the future too, so complete questions are important. – the Tin Man May 10 '17 at 21:08

3 Answers3

6

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%"}
Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • @AndreyDeineko thank you very much! I added `round(2)` to get only 2 numbers after the coma. Could you please explain more the `{ |(k, v), hash| hash[k] = ` so I could understand better .. Thanks – media May 09 '17 at 14:44
  • 1
    @media I edited the answer with the link to documentation. Basically you iterate over each key-value pair (`k`,`v`) and you are creating a new object on the fly `({})` - hash. So prior the first iteration hash is empty (it's initial value we gave). On the first iteration we set the `hash["str1"]` to be equal to the calculated percentage based on the `v` (`2`). On the next iteration we set the `hash["str2"]` to eq next calculation and so on. So in the end we have new hash object. – Andrey Deineko May 09 '17 at 14:49
  • 1
    `'%.2f%' % (v * 100.0 / sum)` might be more readable if you're used to sprintf. – Eric Duminil May 09 '17 at 16:21
  • 1
    @EricDuminil I was 100% sure sooner or later someone will mention that :) I used to interpolation - `'%.2f%'` reminds me of Java which I don't love much. But indeed it might look cleaner to those who used to it – Andrey Deineko May 09 '17 at 16:23
  • In real life you'd need to deal with the case where `sum` equals zero. – Cary Swoveland May 14 '17 at 05:32
5

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.

Mark Thomas
  • 37,131
  • 11
  • 74
  • 101
  • Thank you so much Mark, but my answer was pretty identical to Andrey's answer and I used the amazing `transform_values` method that is too new to put that in an answer without an alternative :) – Ursus May 09 '17 at 18:10
  • Well I think over time it will become more and more useful. Some of my answers which required a certain version of Ruby may not have been too useful at the time, but eventually [became among my highest scoring answers](http://stackoverflow.com/questions/4339553/sort-hash-by-key-return-hash-in-ruby/22315036#22315036). – Mark Thomas May 09 '17 at 19:31
0

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 + "%"}
media
  • 135
  • 1
  • 7