92
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'])
4
  • 1
    Is there a reason why you want to do that in one call? I would prefer to stick with your first solution. Commented Nov 15, 2011 at 7:08
  • 2
    @Semyon: The mapping table couple be large or it could be configured at run time. Commented Nov 15, 2011 at 7: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. Commented Nov 15, 2011 at 7:28
  • I had expected String#tr to do the trick, but the replacements being multiple charcters means I can't use that. Commented Nov 15, 2011 at 22:38

7 Answers 7

139

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*"
Sign up to request clarification or add additional context in comments.

4 Comments

Cool, didn't know about that. Kind of a nicer version of Perl tr.
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.
@NarenSisodiya, actually it should be: '(0) 123-123.123'.gsub(/[()\-,. ]/, '') You need to add the escape character to '-'.
Yeah that line there is wrong: '(0) 123-123.123'.gsub(/[()-,. ]/, '') You either need to escape the dash or move it to the front.
45

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.

6 Comments

@steenslag: That's a nice modification.
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: Yeah but we have multiple strings to replace, hence the regex.
what if the keys of the map are regex expressions? the replacement doesn't seem to work
@content01: Off the top of my head, I think you'd have to go one by one in that case: map.each { |re, v| ... }
|
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

6 Comments

Isn't it suppose to be replacements.each?
This is just more complicated and slower.
The return value for each is the collection it was invoked upon. stackoverflow.com/questions/11596879/…
to have it return the result and not change str: replacements.reduce(str){|str,replacement| str.gsub(replacement[0],replacement[1])}
@artm you can also do replacements.inject(str) { |str, (k,v)| str.gsub(k,v) } and avoid needing to do [0] and [1].
|
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 (-)

1 Comment

this would be even better like this: string_to_replace.gsub(/[_- ]/, '-')
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'

Comments

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)

Comments

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"

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.