Andrew Grimm
Andrew Grimm

Reputation: 81510

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

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)

Upvotes: 8

Views: 344

Answers (3)

the Tin Man
the Tin Man

Reputation: 160551

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"

Upvotes: 1

hammar
hammar

Reputation: 139840

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))

Upvotes: 8

sepp2k
sepp2k

Reputation: 370132

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.

Upvotes: 2

Related Questions