87
str = "Hello☺ World☹"

Expected output is:

"Hello:) World:("

I can do this: str.gsub("☺", ":)").gsub("☹", ":(")

Is there any other way so that I can do this in a single function call?. Something like:

str.gsub(['s1', 's2'], ['r1', 'r2'])
de-russification
  • 31,008
  • 6
  • 34
  • 52
Sayuj
  • 7,246
  • 11
  • 57
  • 75
  • 1
    Is there a reason why you want to do that in one call? I would prefer to stick with your first solution. – Simon Perepelitsa Nov 15 '11 at 07:08
  • 2
    @Semyon: The mapping table couple be large or it could be configured at run time. – mu is too short Nov 15 '11 at 07:15
  • 1
    On a similar note, if you end up having a *huge* mapping table - you are basically looking at a templating language. You can, in that case, convert it into a DSL and write an interpreter (or compiler) for that. – Swanand Nov 15 '11 at 07:28
  • I had expected `String#tr` to do the trick, but the replacements being multiple charcters means I can't use that. – Andrew Grimm Nov 15 '11 at 22:38

7 Answers7

128

Since Ruby 1.9.2, String#gsub accepts hash as a second parameter for replacement with matched keys. You can use a regular expression to match the substring that needs to be replaced and pass hash for values to be replaced.

Like this:

'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*')    #=> "h3ll*"
'(0) 123-123.123'.gsub(/[()-,. ]/, '')    #=> "0123123123"

In Ruby 1.8.7, you would achieve the same with a block:

dict = { 'e' => 3, 'o' => '*' }
'hello'.gsub /[eo]/ do |match|
   dict[match.to_s]
 end #=> "h3ll*"
Dennis
  • 52,060
  • 25
  • 136
  • 135
Naren Sisodiya
  • 6,998
  • 2
  • 23
  • 35
42

Set up a mapping table:

map = {'☺' => ':)', '☹' => ':(' }

Then build a regex:

re = Regexp.new(map.keys.map { |x| Regexp.escape(x) }.join('|'))

And finally, gsub:

s = str.gsub(re, map)

If you're stuck in 1.8 land, then:

s = str.gsub(re) { |m| map[m] }

You need the Regexp.escape in there in case anything you want to replace has a special meaning within a regex. Or, thanks to steenslag, you could use:

re = Regexp.union(map.keys)

and the quoting will be take care of for you.

mu is too short
  • 413,090
  • 67
  • 810
  • 771
  • @steenslag: That's a nice modification. – mu is too short Nov 15 '11 at 07:43
  • String#gsub accepts strings as the pattern parameter: "The pattern is typically a Regexp; if given as a String, any regular expression metacharacters it contains will be interpreted literally, e.g. '\\d' will match a backlash followed by ‘d’, instead of a digit.". – Andrew Grimm Nov 15 '11 at 22:40
  • @Andrew: Yeah but we have multiple strings to replace, hence the regex. – mu is too short Nov 15 '11 at 22:48
  • what if the keys of the map are regex expressions? the replacement doesn't seem to work – content01 Feb 10 '14 at 23:30
  • @content01: Off the top of my head, I think you'd have to go one by one in that case: `map.each { |re, v| ... }` – mu is too short Feb 10 '14 at 23:39
  • I simplified my address abbreviations map like so: `map = Hash[*%w(AVENUE AVE BOULEVARD BLVD STREET ST)]`, except with nice indentation and one pair per row. – Clint Pachl Nov 20 '14 at 23:35
38

You could do something like this:

replacements = [ ["☺", ":)"], ["☹", ":("] ]
replacements.each {|replacement| str.gsub!(replacement[0], replacement[1])}

There may be a more efficient solution, but this at least makes the code a bit cleaner

Sayuj
  • 7,246
  • 11
  • 57
  • 75
Nathan Manousos
  • 12,370
  • 2
  • 23
  • 37
  • 2
    Isn't it suppose to be `replacements.each`? – DanneManne Nov 15 '11 at 07:01
  • 4
    This is just more complicated and slower. – SwiftMango Feb 25 '13 at 06:28
  • When I have this as the last line of a method what would be the implicit return value? str or something else? – San Apr 09 '13 at 13:47
  • 1
    The return value for `each` is the collection it was invoked upon. http://stackoverflow.com/questions/11596879/why-does-arrayeach-return-an-array-with-the-same-elements – Nathan Manousos Apr 09 '13 at 23:39
  • 1
    to have it return the result and not change str: `replacements.reduce(str){|str,replacement| str.gsub(replacement[0],replacement[1])}` – artm May 12 '13 at 20:30
  • 4
    @artm you can also do `replacements.inject(str) { |str, (k,v)| str.gsub(k,v) }` and avoid needing to do `[0]` and `[1]`. – Ben Lings Feb 05 '15 at 09:27
20

Late to the party but if you wanted to replace certain chars with one, you could use a regex

string_to_replace.gsub(/_|,| /, '-')

In this example, gsub is replacing underscores(_), commas (,) or ( ) with a dash (-)

Automatico
  • 11,522
  • 8
  • 74
  • 107
lsaffie
  • 1,751
  • 17
  • 22
8

Another simple way, and yet easy to read is the following:

str = '12 ene 2013'
map = {'ene' => 'jan', 'abr'=>'apr', 'dic'=>'dec'}
map.each {|k,v| str.sub!(k,v)}
puts str # '12 jan 2013'
Diego Dorado
  • 388
  • 4
  • 12
6

You can also use tr to replace multiple characters in a string at once,

Eg., replace "h" to "m" and "l" to "t"

"hello".tr("hl", "mt")
 => "metto"

looks simple, neat and faster (not much difference though) than gsub

puts Benchmark.measure {"hello".tr("hl", "mt") }
  0.000000   0.000000   0.000000 (  0.000007)

puts Benchmark.measure{"hello".gsub(/[hl]/, 'h' => 'm', 'l' => 't') }
  0.000000   0.000000   0.000000 (  0.000021)
YasirAzgar
  • 1,097
  • 16
  • 15
1

Riffing on naren's answer above, I'd go with

tr = {'a' => '1', 'b' => '2', 'z' => '26'}
mystring.gsub(/[#{tr.keys}]/, tr)

So 'zebraazzeebra'.gsub(/[#{tr.keys}]/, tr) returns "26e2r112626ee2r1"

gitb
  • 1,060
  • 2
  • 11
  • 20