3

Suppose we have the string "aaabbbccc" and want to use the String#insert to convert the string to "aaa<strong>bbb</strong>ccc". Is this the best way to insert multiple values into a Ruby string using String#insert or can multiple values simultaneously be added:

string = "aaabbbccc"
opening_tag = '<strong>'
opening_index = 3
closing_tag = '</strong>'
closing_index = 6
string.insert(opening_index, opening_tag)
closing_index = 6 + opening_tag.length # I don't really like this
string.insert(closing_index, closing_tag)

Is there a way to simultaneously insert multiple substrings into a Ruby string so the closing tag does not need to be offset by the length of the first substring that is added? I would like something like this one liner:

string.insert(3 => '<strong>', 6 => '</strong>') # => "aaa<strong>bbb</strong>ccc"
3
  • Why in the world would you limit yourself to String#insert? Regexes would be much cleaner if it's not specifically index-oriented. Commented Feb 14, 2014 at 2:54
  • @syrion - Your question is valid. I have an aversion to Regex because I don't know it well yet...maybe it's time to learn. Commented Feb 14, 2014 at 3:11
  • 1
    In this case, it'd make things much easier: "aaabbbccc".sub(/(bbb)/, '<strong>\1</strong>'). Of course, that presumes that you're just replacing the bbb specifically, but it can be modified to your needs and even regex is going to be less inscrutable than String#insert acrobatics. :) Commented Feb 14, 2014 at 3:23

4 Answers 4

4

Let's have some fun. How about

class String
  def splice h
    self.each_char.with_index.inject('') do |accum,(c,i)|
      accum + h.fetch(i,'') + c
    end  
  end  
end  

"aaabbbccc".splice(3=>"<strong>", 6=>"</strong>")
=> "aaa<strong>bbb</strong>ccc"

(you can encapsulate this however you want, I just like messing with built-ins because Ruby lets me)

Sign up to request clarification or add additional context in comments.

3 Comments

+1 Nice answer. Fyi you can replace += with just +.
@Matt thanks. The += was a remnant from when I had two lines in the block.
@roippi awesome answer. But it seems like "aaabbbccc".splice(9=>"</strong>") will not work
1

How about inserting from right to left?

string = "aaabbbccc"
string.insert(6, '</strong>')
string.insert(3, '<strong>')
string # => "aaa<strong>bbb</strong>ccc"

2 Comments

Right to left is certainly a better approach - thanks :) So does that mean there is no way to insert multiple values simultaneously with Ruby?
@Powers, I don't know a way to insert multiple values at specific positions. But if your intention is to wrap bbb with <strong>..</strong>, you can use String#gsub: string.gsub!(/bbb/, '<strong>\0</strong>')
1
opening_tag = '<strong>'
opening_index = 3
closing_tag = '</strong>'
closing_index = 6

string = "aaabbbccc"
string[opening_index...closing_index] = 
   opening_tag + string[opening_index...closing_index] + closing_tag
#=> "<strong>bbb</strong>"
string
#=> "aaa<strong>bbb</strong>ccc"

1 Comment

Using string[opening_index...closing_index], you don't need to calculate the length.
0

If your string is comprised of three groups of consecutive characters, and you'd like to insert the opening tag between the first two groups and the closing tag between the last two groups, regardless of the size of each group, you could do that like this:

def stuff_tags(str, tag)    
  str.scan(/((.)\2*)/)
    .map(&:first)
    .insert( 1, "<#{tag}>")
    .insert(-2, "<\/#{tag}>")
    .join
end

stuff_tags('aaabbbccc', 'strong')  #=> "aaa<strong>bbb</strong>ccc"
stuff_tags('aabbbbcccccc', 'weak') #=> "aa<weak>bbbb</weak>cccccc"

I will explain the regex used by scan, but first would like to show how the calculations proceed for the string 'aaabbbccc':

a = 'aaabbbccc'.scan(/((.)\2*)/)
  #=> [["aaa", "a"], ["bbb", "b"], ["ccc", "c"]]
b = a.map(&:first)
  #=> ["aaa", "bbb", "ccc"] 
c = b.insert( 1, "<strong>")
  #=> ["aaa", "<strong>", "bbb", "ccc"]
d = c.insert(-2, "<\/strong>")
  #=> ["aaa", "<strong>", "bbb", "</strong>", "ccc"]
d.join
  #=> "aaa<strong>bbb</strong>ccc"

We need two capture groups in the regex. The first (having the first left parenthesis) captures the string we want. The second captures the first character, (.). This is needed so that we can require that it be followed by zero or more copies of that character, \2*.

Here's another way this can be done:

def stuff_tags(str, tag)    
  str.chars.chunk {|c| c}
    .map {|_,a| a.join}
    .insert( 1, "<#{tag}>")
    .insert(-2, "<\/#{tag}>")
    .join
end

The calculations of a and b above change to the following:

a = 'aaabbbccc'.chars.chunk {|c| c}
  #=> #<Enumerator: #<Enumerator::Generator:0x000001021622d8>:each>
  # a.to_a => [["a",["a","a","a"]],["b",["b","b","b"]],["c",["c","c","c"]]]
b = a.map {|_,a| a.join }
  #=> ["aaa", "bbb", "ccc"] 

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.