Ruby equivalent to the C # keyword 'yield', or by creating sequences without prior memory

In C #, you can do something like this:

public IEnumerable<T> GetItems<T>() { for (int i=0; i<10000000; i++) { yield return i; } } 

This returns an enumerated sequence of 10 million integers without allocating such a length in memory.

Is there a way to do the equivalent thing in Ruby? The specific example I'm trying to solve is smoothing a rectangular array into a sequence of values ​​to be enumerated. The return value should not be Array or Set , but rather some sequence that can only be repeated / listed in order, not an index. Therefore, the entire sequence should not be allocated in memory at the same time. In .NET, these are IEnumerable and IEnumerable<T> .

Any clarification of the terminology used here in the Ruby world would be helpful, as I am more familiar with .NET terminology.

EDIT

Perhaps my initial question is not entirely clear - I think the fact that yield has very different meanings in C # and Ruby is the cause of the confusion here.

I don't need a solution that requires my method to use a block. I want a solution that has an actual return value. The return value allows convenient sequence processing (filtering, projecting, concatenation, zipping, etc.).

Here is a simple example of how I can use get_items :

 things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing } 

In C #, any method that returns IEnumerable that uses yield return causes the compiler to generate a state machine behind the scenes that is suitable for this behavior. I suspect something like this could be achieved using Ruby extensions, but I have not seen an example and do not quite understand how this will be done.

Indeed, it is possible that I can use Enumerable to achieve this. A simple solution would be for us Array (including the Enumerable module), but I do not want to create an intermediate collection with N elements in memory, when you can simply provide them lazily and avoid a burst of memory at all.

If this still doesn't make sense, consider the above code example. get_items returns the enumeration on which select is invoked. What is passed to select is an instance that knows how to provide the next element in the sequence whenever necessary. It is important to note that the entire set of items has not yet been calculated. Only when select needs an element will it ask for it, and the hidden code in get_items will act and provide it. This laziness is carried along the chain, so select only draws the next element from the sequence when map requests it. Thus, a long chain of operations can be performed simultaneously on one data element. In fact, code structured in this way can even process an infinite sequence of values ​​without any memory errors.

So, this type of laziness is easily encoded in C #, and I don't know how to do it in Ruby.

I hope that is clearer (I will try not to write questions in 3AM in the future).

+7
yield ruby sequences
source share
4 answers

It is supported by Enumerator with Ruby 1.9 (and is back ported to 1.8.7). See Generator: Ruby .

Cliche example:

 fib = Enumerator.new do |y| y.yield i = 0 y.yield j = 1 while true k = i + j y.yield k i = j j = k end end 100.times { puts fib.next() } 
+14
source share

Your specific example is equivalent to 10000000.times , but suppose for a moment that the time method does not exist, and you would like to implement it yourself, it would look like this:

 class Integer def my_times return enum_for(:my_times) unless block_given? i=0 while i<self yield i i += 1 end end end 10000.my_times # Returns an Enumerable which will let # you iterate of the numbers from 0 to 10000 (exclusive) 

Edit: clarify my answer a bit:

In the above example, my_times can be (and is used) without a block and will return an Enumerable object that allows you to iterate over numbers from 0 to n. So this is exactly equivalent to your example in C #.

This works using the enum_for method. The enum_for method takes as an argument the name of the method that will give some elements. Then it returns an instance of the Enumerator class (which includes the Enumerable module), which, when executed again, will execute this method and provide you with the elements that were obtained by the method. Please note that if you only iterate over the first elements x of an enumerated one, the method will only be executed until the elements x are received (i.e. it will only be executed as long as necessary for the method), and if you repeat the enumerated twice, the method will be executed twice.

In 1.8.7+, he began to define methods that give elements, so when called without a block, they return an Enumerator, which allows the user to iterate over these elements lazily. This is done by adding the line return enum_for(:name_of_this_method) unless block_given? to the beginning of the method, as it was in my example.

+5
source share

Without a lot of Ruby experience, what C # does in yield return is usually called lazy evaluation or lazy execution : providing answers only when necessary. This is not about the distribution of memory, but about calculating the delay to the actual need, expressed in a way similar to simple linear execution (and not the basic iterator with state preservation).

Fast google has opened a beta version of ruby library . See what you want.

+1
source share

C # tore up the "yield" keyword directly from Ruby - see iterator implementation here for more.

As for your real problem, do you have an array of arrays and want to create a one-way iteration over the entire length of the list? It might be worth looking at array.flatten as a starting point - if the performance is fine, you probably don't need to go too much further.

-2
source share

All Articles