88
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'])
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Sayuj
  • 7,464
  • 13
  • 59
  • 76
  • 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

133

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
  • 56,821
  • 26
  • 143
  • 139
Naren Sisodiya
  • 7,158
  • 2
  • 24
  • 35
  • Cool, didn't know about that. Kind of a nicer version of Perl `tr`. – Marnen Laibow-Koser Nov 15 '11 at 15:30
  • Note that this is not the same as calling str.gsub(key, value) on every element of the hash. If something is matched by the regexp but doesn't have an entry in the hash, it will be deleted. – Sprachprofi Dec 05 '13 at 19:34
  • 3
    @NarenSisodiya, actually it should be: '(0) 123-123.123'.gsub(/[()\-,. ]/, '') You need to add the escape character to '-'. – jpbalarini Aug 14 '15 at 15:42
  • Yeah that line there is wrong: '(0) 123-123.123'.gsub(/[()-,. ]/, '') You either need to escape the dash or move it to the front. – Greg Blass Aug 16 '16 at 15:47
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
  • 426,620
  • 70
  • 833
  • 800
  • @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
  • 1
    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,464
  • 13
  • 59
  • 76
Nathan Manousos
  • 13,328
  • 2
  • 27
  • 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
  • 12,420
  • 9
  • 82
  • 110
lsaffie
  • 1,764
  • 1
  • 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
  • 398
  • 4
  • 12
7

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,385
  • 16
  • 15
4

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,090
  • 2
  • 12
  • 20