Changing every value in a hash in Ruby
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)
theReverseFlick
Updated on April 21, 2020Comments
-
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 over 13 yearsPlease clarify if you want to mutate the original string objects, just mutate the original has, or mutate nothing.
-
-
Admin over 13 yearsI 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 andMap#merge
may also work. Not sure how the intricate details differ. -
oligan over 13 yearsDoes Matz even know if
Hash.map
semantics change in Ruby 2.x? -
Admin over 13 yearsThe 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 over 13 yearsWhy do you need the
flatten
? Also don't you meanv
, notstr
in your last excerpt? -
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 over 13 yearsActually, Hash.[key_value_pairs] was introduced in 1.8.7, so only Ruby 1.8.6 doesn't needs the splat & flatten.
-
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 almost 13 yearsWhat's the
_
in this context? -
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 almost 13 years@Phrogz Ah, thanks. I thought
_
might mean something special. You never know, with Ruby :) -
aceofspades almost 12 yearsI 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 about 11 yearsAlternately, you might use the each_value method, which is a little easier to understand than using an underscore for the unused key value.
-
Martijn almost 11 yearsThe Hash#[] method is so useful, but so ugly. Is there a prettier method of converting arrays to hashes in the same way?
-
user229044 almost 11 yearsClearer to use
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
-
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 almost 10 yearsThis is an extremely inefficient way to update values. For every value pair it first creates a pair
Array
(formap
) then aHash
. 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!
inreduce
to modify the finalHash
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 almost 10 yearsNot exactly true: new strings are allocated. Still, an interesting solution that is effective. +1
-
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 over 9 yearsSame as my answer but with another synonym, I agree that
update
conveys the intention better thanmerge!
. I think this is the best answer. -
Jeremy Lewis about 9 yearsActually, this isn't available until Ruby v2.1
-
Jeremy Lewis about 9 years@shock_one,
to_h
is available onHash
but notArray
, which is whatmap
returns. -
Andrew Hodgkinson almost 9 yearsThis 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 over 8 yearsit returns
nil
ifthe_hash
is empty -
oligan about 8 yearsNot that I have anything against Ruby 1.8.6, but this answer is a bit dated now. See also shock_one's answer.
-
amoebe over 7 yearsAlso Ruby 2.4.0+ contains
Hash#transform_values
. This should be the way to go from now on. -
Cyril Duchon-Doris over 7 yearsIn a nutshell, available for Rails 5+ or Ruby 2.4+
-
sekrett about 7 yearsIf you don't use
k
, use_
instead. -
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 about 7 yearsThe 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 about 7 yearsExists in 4.0+ github.com/rails/rails/blob/4-0-stable/activesupport/lib/…
-
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 over 6 yearsIf you use Ruby 2.4+, it's even easier to use
#transform_values!
as pointed out by sschmeck (stackoverflow.com/a/41508214/6451879). -
iGEL about 5 yearsOf course there is also
Hash#transform_values
(without the bang), which doesn't modify the receiver. Otherwise a great answer, thanks! -
iGEL about 5 yearsThis will really reduce my use of
reduce
:-p -
Zoë Sparks over 2 yearsSo, 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.