2

I'm looking for a way to substitute words and phrases in a string from a hash. I know that this can be done elegantly in Ruby > 1.9 for single words:

a = "every good boy does fine"
h = {"every" => "all","boy" => "girl", "fine" =>"well" }
a.gsub(/\w+/) { |m| h.fetch(m,m)}

https://stackoverflow.com/a/20650800/4530434

However this doesn't work if the phrase I want substituted is more than one word. What I want is something like this, ideally working with arbitrarily long string substitutions, but I'd settle for two (or three) word phrases.

a = "I love the eighteen nineties"
b = {"eighteen nineties" => "1890's" }
# => "I love the 1890's"

I've tried fiddling with the Regexp in the code above, but couldn't get anythig to work.

Community
  • 1
  • 1
sarkon
  • 187
  • 2
  • 8
  • I've done some investigating, and it seems it would be impossible to create a regex which selects all possible combinations of consecutive words because once there is a match, it's impossible to reuse that match in another match. ie there isn't any regex which would find the matches for "big bad wolf" to be "big bad" & "bad wolf". (dun dun dun) – 4castle Mar 22 '16 at 01:50
  • I should have been clearer. What I meant by "phrases" is consecutive words. I don't need all combinations. – sarkon Mar 22 '16 at 02:41
  • Sure, but I'm saying there's no way to match two words in the same way you are matching one word. There is no regex which you can simply insert into the code you're using in order to solve the problem. You will have to use the regex differently. I'm just trying to generalize the answers that this question will have. It's a very good question. – 4castle Mar 22 '16 at 03:07

3 Answers3

1

Try iterating through each value in in the hash:

>> a = "I love the eighteen nineties"
>> b = {"eighteen nineties" => "1890's", "love" => "enjoy" }

>> b.inject(a) {|s, (k, v)| s.gsub(k, v)}
=> "I enjoy the 1890's"
user12341234
  • 6,573
  • 6
  • 23
  • 48
1

Make a pattern based on the mapping:

text = "I love the eighteen nineties"
mapping = {"eighteen nineties" => "1890's"}
pattern = /\b#{Regexp.union(mapping.keys)}\b/
text.gsub(pattern, mapping)
# => "I love the 1890's"
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • 1
    It seems like `/\b#{Regexp.union(mapping.keys)}\b/` is a lot cleaner and does the same thing. – Jordan Running Mar 22 '16 at 02:25
  • @Jordan, Thank you. I updated the answer according to your suggestion. – falsetru Mar 22 '16 at 02:27
  • 1
    You can just pass the hash as the second argument to `gsub`, too. – Jordan Running Mar 22 '16 at 02:54
  • @Jordan, Nice. Thanks again :) I didn't know `String#gsub` accept hash as the second parameter. – falsetru Mar 22 '16 at 02:55
  • That's great, thanks. Also good (and to my eyes more elegant) is user12341234's answer below -- though I haven't done any speed comparisons. I accepted this one because it came closer to just extending the example I gave. – sarkon Mar 22 '16 at 17:59
0

Is this what you had in mind? Note that gsub! is destructive and will permanently replace the string in a. This loops through each hash key, and where the hash key is found in a, it will replace it with the value stored in the hash relative to the key.

irb(main):001:0> a = "I love the eighteen nineties"
=> "I love the eighteen nineties"
irb(main):002:0> b = {"eighteen nineties" => "1890's" }
=> {"eighteen nineties"=>"1890's"}
irb(main):003:0> b.keys.each { |k| a.gsub!(k, b[k]) }
=> ["eighteen nineties"]
irb(main):004:0> a
=> "I love the 1890's"
JLB
  • 323
  • 3
  • 8