I want to change every value in a hash so as to add '%' before and after the value so
{ :a=>'a' , :b=>'b' }
must be changed to
{ :a=>'%a%' , :b=>'%b%' }
What's the best way to do this?
I want to change every value in a hash so as to add '%' before and after the value so
{ :a=>'a' , :b=>'b' }
must be changed to
{ :a=>'%a%' , :b=>'%b%' }
What's the best way to do this?
In Ruby 2.1 and higher you can do
{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
#transform_values! as pointed out by sschmeck (stackoverflow.com/a/41508214/6451879).If you want the actual strings themselves to mutate in place (possibly and desirably affecting other references to the same string objects):
# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }
If you want the hash to change in place, but you don't want to affect the strings (you want it to get new strings):
# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }
If you want a new hash:
# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]
# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]
Hash.[] doesn't accept an array of array pairs, it requires an even number of direct arguments (hence the splat up front).Hash#each yields both the key and the value to the block. In this case, I didn't care about the key, and so I didn't name it anything useful. Variable names may begin with an underscore, and in fact may be just an underscore. There is no performance benefit of doing this, it's just a subtle self-documenting note that I'm not doing anything with that first block value.my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, have to return the hash from the blockRuby 2.4 introduced the method Hash#transform_values!, which you could use.
{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"}
Hash#transform_values (without the bang), which doesn't modify the receiver. Otherwise a great answer, thanks!The best way to modify a Hash's values in place is
hash.update(hash){ |_,v| "%#{v}%" }
Less code and clear intent. Also faster because no new objects are allocated beyond the values that must be changed.
gsub!.k, use _ instead.A bit more readable one, map it to an array of single-element hashes and reduce that with merge
the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]Array (for map) then a Hash. Then, each step of the reduce operation will duplicate the "memo" Hash and add the new key-value pair to it. At least use :merge! in reduce to modify the final Hash in place. And in the end, you are not modifying the values of the existing object but creating a new object, which is not what the question asked.nil if the_hash is emptyThere is a new 'Rails way' method for this task :) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values
Hash#transform_values. This should be the way to go from now on.One method that doesn't introduce side-effects to the original:
h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]
Hash#map may also be an interesting read as it explains why the Hash.map doesn't return a Hash (which is why the resultant Array of [key,value] pairs is converted into a new Hash) and provides alternative approaches to the same general pattern.
Happy coding.
[Disclaimer: I am not sure if Hash.map semantics change in Ruby 2.x]
Hash.map semantics change in Ruby 2.x?my_hash.each do |key, value|
my_hash[key] = "%#{value}%"
end
After testing it with RSpec like this:
describe Hash do
describe :map_values do
it 'should map the values' do
expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
end
end
end
You could implement Hash#map_values as follows:
class Hash
def map_values
Hash[map { |k, v| [k, yield(v)] }]
end
end
The function then can be used like this:
{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
If you are curious which inplace variant is the fastest here it is:
Calculating -------------------------------------
inplace transform_values! 1.265k (± 0.7%) i/s - 6.426k in 5.080305s
inplace update 1.300k (± 2.7%) i/s - 6.579k in 5.065925s
inplace map reduce 281.367 (± 1.1%) i/s - 1.431k in 5.086477s
inplace merge! 1.305k (± 0.4%) i/s - 6.630k in 5.080751s
inplace each 1.073k (± 0.7%) i/s - 5.457k in 5.084044s
inplace inject 697.178 (± 0.9%) i/s - 3.519k in 5.047857s