Changing every value in a hash in Ruby

160,704

Solution 1

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

Solution 2

In Ruby 2.1 and higher you can do

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

Solution 3

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

Solution 4

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.

Solution 5

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)
Share:
160,704
theReverseFlick
Author by

theReverseFlick

Updated on April 21, 2020

Comments

  • theReverseFlick
    theReverseFlick about 4 years

    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?

    • Phrogz
      Phrogz over 13 years
      Please clarify if you want to mutate the original string objects, just mutate the original has, or mutate nothing.
  • Admin
    Admin over 13 years
    I don't like side-effects, but +1 for the approach :) There is each_with_object in Ruby 1.9 (IIRC) which avoids needing to access the name directly and Map#merge may also work. Not sure how the intricate details differ.
  • oligan
    oligan over 13 years
    Does Matz even know if Hash.map semantics change in Ruby 2.x?
  • Admin
    Admin over 13 years
    The initial hash is modified -- this is okay if the behavior is anticipated but can cause subtle issues if "forgotten". I prefer to reduce object mutability, but it may not always be practical. (Ruby is hardly a "side-effect-free" language ;-)
  • Andrew Marshall
    Andrew Marshall over 13 years
    Why do you need the flatten? Also don't you mean v, not str in your last excerpt?
  • Phrogz
    Phrogz over 13 years
    @Andrew Marshall Right you are, thanks. In Ruby 1.8, Hash.[] doesn't accept an array of array pairs, it requires an even number of direct arguments (hence the splat up front).
  • Marc-André Lafortune
    Marc-André Lafortune over 13 years
    Actually, Hash.[key_value_pairs] was introduced in 1.8.7, so only Ruby 1.8.6 doesn't needs the splat & flatten.
  • Phrogz
    Phrogz over 13 years
    @Marc Thanks for the clarification; 1.8.7 is such an odd beast, more like 1.9 than 1.8.6. With all its changes, I just went straight to 1.9. I've updated the comments to match.
  • jack fernandz
    jack fernandz almost 13 years
    What's the _ in this context?
  • Phrogz
    Phrogz almost 13 years
    @Aupajo 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.
  • jack fernandz
    jack fernandz almost 13 years
    @Phrogz Ah, thanks. I thought _ might mean something special. You never know, with Ruby :)
  • aceofspades
    aceofspades almost 12 years
    I think you mean my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, have to return the hash from the block
  • Strand McCutchen
    Strand McCutchen about 11 years
    Alternately, you might use the each_value method, which is a little easier to understand than using an underscore for the unused key value.
  • Martijn
    Martijn almost 11 years
    The Hash#[] method is so useful, but so ugly. Is there a prettier method of converting arrays to hashes in the same way?
  • user229044
    user229044 almost 11 years
    Clearer to use Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
  • Andrew Marshall
    Andrew Marshall about 10 years
    @Aupajo Actually, _ when used in an argument list is a bit special. See Where and how is the _ (underscore) variable specified?.
  • Sim
    Sim almost 10 years
    This is an extremely inefficient way to update values. For every value pair it first creates a pair 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.
  • Phrogz
    Phrogz almost 10 years
    Not exactly true: new strings are allocated. Still, an interesting solution that is effective. +1
  • Sim
    Sim almost 10 years
    @Phrogz good point; I updated the answer. The value allocation cannot be avoided in general because not all value transforms can be expressed as mutators such as gsub!.
  • Admin
    Admin over 9 years
    Same as my answer but with another synonym, I agree that update conveys the intention better than merge!. I think this is the best answer.
  • Jeremy Lewis
    Jeremy Lewis about 9 years
    Actually, this isn't available until Ruby v2.1
  • Jeremy Lewis
    Jeremy Lewis about 9 years
    @shock_one, to_h is available on Hash but not Array, which is what map returns.
  • Andrew Hodgkinson
    Andrew Hodgkinson almost 9 years
    This is, though, very slow and very RAM hungry. The input Hash is iterated over to produce an intermediate set of nested Arrays which are then converted into a new Hash. Ignoring the RAM peak usage, run time is much worse - benchmarking this versus the modify-in-place solutions in another answer show 2.5s versus 1.5s over the same number of iterations. Since Ruby is a comparatively slow language, avoiding slow bits of the slow language makes a lot of sense :-)
  • DNNX
    DNNX over 8 years
    it returns nil if the_hash is empty
  • oligan
    oligan about 8 years
    Not that I have anything against Ruby 1.8.6, but this answer is a bit dated now. See also shock_one's answer.
  • amoebe
    amoebe over 7 years
    Also Ruby 2.4.0+ contains Hash#transform_values. This should be the way to go from now on.
  • Cyril Duchon-Doris
    Cyril Duchon-Doris over 7 years
    In a nutshell, available for Rails 5+ or Ruby 2.4+
  • sekrett
    sekrett about 7 years
    If you don't use k, use _ instead.
  • elsurudo
    elsurudo about 7 years
    @AndrewHodgkinson while in general I agree and am not advocating not paying attention to runtime performance, doesn't keeping track of all these performance pitfalls begin to become a pain and go against the "developer productivity first" philosophy of ruby? I guess this is less of a comment to you, and more of a general comment on the eventual paradox this brings us to, using ruby.
  • elsurudo
    elsurudo about 7 years
    The conundrum being: well, we're already giving up performance in our decision to even use ruby, so what difference does "this other little bit" make? It's a slippery slope, ain't it? For the record, I do prefer this solution to the accepted answer, from a readability perspective.
  • toxaq
    toxaq about 7 years
  • Andrew Hodgkinson
    Andrew Hodgkinson almost 7 years
    (Belatedly) I guess it's a question of how mentally burdensome this stuff is to you. Perhaps it's just because of my "upbringing" in the likes of the C language and embedded systems, that as soon as I see temporary arrays being built inside iterators all the alarm bells go off. It didn't even seem particularly legible to me, but it's a matter of style. Ultimately the choice will depend on a host of factors - often these days, efficiency comes second place to other concerns. Then again, this may be the reason why a 16GB laptop can barely run Word & a web browser :-P
  • Finn
    Finn over 6 years
    If you use Ruby 2.4+, it's even easier to use #transform_values! as pointed out by sschmeck (stackoverflow.com/a/41508214/6451879).
  • iGEL
    iGEL about 5 years
    Of course there is also Hash#transform_values (without the bang), which doesn't modify the receiver. Otherwise a great answer, thanks!
  • iGEL
    iGEL about 5 years
    This will really reduce my use of reduce :-p
  • Zoë Sparks
    Zoë Sparks over 2 years
    So, I know I'm chiming in 5 years late, but just in case anyone ever comes across this discussion, please remember that going between C (and C++! even Rust! whatever really) and Ruby is pretty straightforward, so if some part of your Ruby codebase is unnacceptably slow you can just write that part in a more optimization-oriented language. Even in performance-intensive contexts, this tends to only be necessary in particular areas of the program; the rest can program can be written in even "inneficient" Ruby with little consequence, allowing you to bask in the niceness of Ruby in those places.