Home > database >  Do something x times per second
Do something x times per second

Time:01-26

I've been given an interesting ruby question for a technical test. I did not find an answer in the time given, but i think it was interesting and would like someone else's take on this. How would you write code to do:

2.times.each_second { p 'works' }

I tried extending the enumerator class, with a per_second method. The per_second method just sleeps for the correct number of milliseconds. But i was stuck trying to pass the "2" argument to the per_second method in an elegant fashion. Maybe using the length of the enumerator returned from 2.times?

CodePudding user response:

Using the magic of modern Ruby, we can write a refinement to Integer that augments the times method, but only in scopes that opt into this refinement behavior.

module IntegerExt

  # Define a refinement of the Integer type. This is like
  # monkeypatching but is local to modules or files that opt in to the
  # change.
  refine Integer do
    # Re-write the times function.
    def times(&block)
      if block
        # If a block was provided, then do the "normal" thing.
        super(&block)
      else
        # Otherwise, get an enumerator and augment its singleton class
        # with PerSecondEnumerator.
        super().extend PerSecondEnumerator
      end
    end
  end

  # This module provides the per_second method on enumerators that
  # need it.
  module PerSecondEnumerator
    def per_second(&block)
      loop do
        sleep(1.0/self.size)
        block.call
      end
    end
  end

end

# If a module wishes to have this functionality, it needs to opt in
# with this 'using' statement. The per_second syntax only works on
# objects constructed within the lexical scope of this refinement.
using IntegerExt

puts 2.times.inspect # Still works
2.times.per_second { p 'works' }

This deals with a couple of the concerns of Aria's answer. We're no longer globally monkey-patching a built-in class. We only modify the class in the lexical scope of any module that wishes to see our changes. Further, since we've refined times rather than Enumerator directly, our per_second only works on enumerators constructed via times, so it's impossible to do something nonsensical like [1, 2, 3, 4, 5].per_second { ... }.

CodePudding user response:

You could do it like that:

Enumerator.define_method(:per_second) do |&block|
  loop do
    sleep(1.0/self.size)
    block.call
  end
end

3.times.per_second { puts "works" }

But here are some warnings:

  1. It's not recommended to expand a Ruby base class like Enumerator, Integer, etc.

  2. Like someone said in a comment on your post, it's not good to use size, since you can use a string or array instead of a integer.

  •  Tags:  
  • ruby
  • Related