theReverseFlick
theReverseFlick

Reputation: 6054

Changing every value in a hash in Ruby

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?

Upvotes: 205

Views: 182452

Answers (11)

lzap
lzap

Reputation: 17173

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

Upvotes: 3

Sim
Sim

Reputation: 13548

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.

Upvotes: 92

Phrogz
Phrogz

Reputation: 303520

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}%"] } ]

Upvotes: 193

sschmeck
sschmeck

Reputation: 7715

Ruby 2.4 introduced the method Hash#transform_values!, which you could use.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

Upvotes: 182

user1115652
user1115652

Reputation:

Hash.merge! is the cleanest solution

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

Upvotes: 8

wedesoft
wedesoft

Reputation: 2989

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%"}

Upvotes: 5

woto
woto

Reputation: 3077

There is a new 'Rails way' method for this task :) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values

Upvotes: 19

shock_one
shock_one

Reputation: 5925

In Ruby 2.1 and higher you can do

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

Upvotes: 292

edgerunner
edgerunner

Reputation: 14983

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)

Upvotes: 28

Andrew Marshall
Andrew Marshall

Reputation: 97004

my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

Upvotes: 16

user166390
user166390

Reputation:

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]

Upvotes: 17

Related Questions