Search code examples
rubyblock

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)


Solution

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