Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I limit the number of times a block is called?

Tags:

ruby

block

In How do I limit the number of replacements when using gsub?, someone suggested the following way to do a limited number of substitutions:

str = 'aaaaaaaaaa'
count = 5
p str.gsub(/a/){if count.zero? then $& else count -= 1; 'x' end}
# => "xxxxxaaaaa"

It works, but the code mixes up how many times to substitute (5) with what the substitution should be ("x" if there should be a substitution, $& otherwise). Is it possible to seperate the two out?

(If it's too hard to seperate the two things out in this scenario, but it can be done in some other scenarios, post that as an answer)

like image 209
Andrew Grimm Avatar asked May 16 '11 23:05

Andrew Grimm


3 Answers

How about just extracting the replacement as an argument and encapsulating the counter by having the block close over it inside a method?

str = "aaaaaaaaaaaaaaa"

def replacements(replacement, limit)
    count = limit
    lambda { |original| if count.zero? then original else count -= 1; replacement end }
end

p str.gsub(/a/, &replacements("x", 5))

You can make it even more general by using a block for the replacement:

def limit(n, &block)
    count = n
    lambda do |original|
        if count.zero? then original else count -= 1; block.call(original) end
    end
end

Now you can do stuff like

p str.gsub(/a/, &limit(5) { "x" })
p str.gsub(/a/, &limit(5, &:upcase))
like image 65
hammar Avatar answered Nov 12 '22 03:11

hammar


gsub will call the block exactly as often as the regex matches the string. The only way to prevent that is to call break in the block, however that will also keep gsub from producing a meaningful return value.

So no, unless you call break in the block (which prevents any further code in the yielding method from running and thus prevents the method from returning anything), the number of times a method calls a block is solely determined by the method itself. So if you want gsub to yield only 5 times, the only way to do that is to pass in a regex which only matches the given strings five times.

like image 33
sepp2k Avatar answered Nov 12 '22 03:11

sepp2k


Why are you using gsub()? By its design, gsub is designed to replace all occurrences of something, so, right off the bat you're fighting it.

Use sub instead:

str = 'aaaaaaaaaa'
count = 5
count.times { str.sub!(/a/, 'x') }
p str
# >> "xxxxxaaaaa"

str = 'mississippi'
2.times { str.sub!(/s/, '5') }
2.times { str.sub!(/s/, 'S') }
2.times { str.sub!(/i/, '1') }
p str
# >> "m1551SSippi"
like image 35
the Tin Man Avatar answered Nov 12 '22 04:11

the Tin Man