202

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?

Jakub Hampl
  • 39,863
  • 10
  • 77
  • 106
theReverseFlick
  • 5,894
  • 8
  • 32
  • 33
  • 1
    Please clarify if you want to mutate the original string objects, just mutate the original has, or mutate nothing. – Phrogz Mar 04 '11 at 03:14

11 Answers11

290

In Ruby 2.1 and higher you can do

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
shock_one
  • 5,845
  • 3
  • 28
  • 39
  • Actually, this isn't available until Ruby v2.1 – Jeremy Lewis May 19 '15 at 00:25
  • @shock_one, `to_h` is available on `Hash` but not `Array`, which is what `map` returns. – Jeremy Lewis May 19 '15 at 22:00
  • 8
    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 :-) – Andrew Hodgkinson Jul 02 '15 at 01:32
  • 3
    @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 May 22 '17 at 19:59
  • 4
    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. – elsurudo May 22 '17 at 20:01
  • (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 – Andrew Hodgkinson Jul 10 '17 at 21:53
  • 6
    If you use Ruby 2.4+, it's even easier to use `#transform_values!` as pointed out by sschmeck (https://stackoverflow.com/a/41508214/6451879). – Finn Jan 09 '18 at 18:35
  • 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. – Zoë Sparks Jan 28 '22 at 23:18
193

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}%"] } ]
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Why do you need the `flatten`? Also don't you mean `v`, not `str` in your last excerpt? – Andrew Marshall Mar 04 '11 at 03:35
  • 1
    @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). – Phrogz Mar 04 '11 at 03:47
  • 2
    Actually, Hash.[key_value_pairs] was introduced in 1.8.7, so only Ruby 1.8.6 doesn't needs the splat & flatten. – Marc-André Lafortune Mar 04 '11 at 05:50
  • @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. – Phrogz Mar 04 '11 at 13:47
  • What's the `_` in this context? – Aupajo Sep 01 '11 at 01:52
  • 2
    @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. – Phrogz Sep 01 '11 at 13:39
  • @Phrogz Ah, thanks. I thought `_` might mean something special. You never know, with Ruby :) – Aupajo Sep 07 '11 at 08:12
  • 1
    I think you mean `my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }`, have to return the hash from the block – aceofspades Sep 25 '12 at 23:57
  • 2
    Alternately, you might use the each_value method, which is a little easier to understand than using an underscore for the unused key value. – Strand McCutchen May 17 '13 at 17:29
  • 1
    @Aupajo Actually, `_` when used in an argument list *is* a bit special. See [Where and how is the _ (underscore) variable specified?](http://stackoverflow.com/q/9559561/211563). – Andrew Marshall Apr 09 '14 at 03:26
  • Not that I have anything against Ruby 1.8.6, but this answer is a bit dated now. See also shock_one's answer. – Andrew Grimm Jun 20 '16 at 02:00
175

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%"} 
sschmeck
  • 7,233
  • 4
  • 40
  • 67
  • 13
    Of course there is also [`Hash#transform_values`](https://docs.ruby-lang.org/en/2.4.0/Hash.html#method-i-transform_values) (without the bang), which doesn't modify the receiver. Otherwise a great answer, thanks! – iGEL Apr 18 '19 at 13:41
  • 4
    This will really *reduce* my use of [`reduce`](https://docs.ruby-lang.org/en/2.4.0/Enumerable.html#method-i-reduce) :-p – iGEL Apr 18 '19 at 13:43
91

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.

Sim
  • 13,147
  • 9
  • 66
  • 95
  • Not exactly true: new strings are allocated. Still, an interesting solution that is effective. +1 – Phrogz Sep 13 '14 at 03:31
  • @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!`. – Sim Sep 13 '14 at 05:22
  • 3
    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. –  Dec 30 '14 at 01:40
  • 1
    If you don't use `k`, use `_` instead. – sekrett Apr 14 '17 at 11:58
28

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)
edgerunner
  • 14,873
  • 2
  • 57
  • 69
  • 2
    Clearer to use `Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]` – user229044 Aug 26 '13 at 19:18
  • 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. – Sim Sep 12 '14 at 23:12
  • it returns `nil` if `the_hash` is empty – DNNX Dec 30 '15 at 10:57
19

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

woto
  • 2,893
  • 1
  • 29
  • 24
  • 9
    Also Ruby 2.4.0+ contains [`Hash#transform_values`](https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values). This should be the way to go from now on. – amoebe Dec 30 '16 at 14:26
  • 1
    In a nutshell, available for Rails 5+ or Ruby 2.4+ – Cyril Duchon-Doris Jan 20 '17 at 00:38
  • Exists in 4.0+ https://github.com/rails/rails/blob/4-0-stable/activesupport/lib/active_support/core_ext/hash/keys.rb – toxaq May 24 '17 at 02:49
17

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]

16
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • 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. –  Mar 04 '11 at 03:14
  • 1
    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 ;-) –  Mar 04 '11 at 03:33
8

Hash.merge! is the cleanest solution

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

puts o.inspect
> { :a => "%a%", :b => "%b%" }
Dorian
  • 22,759
  • 8
  • 120
  • 116
5

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%"}
wedesoft
  • 2,781
  • 28
  • 25
3

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
lzap
  • 16,417
  • 12
  • 71
  • 108