Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Combining enumerators, making a new enumerator

Tags:

ruby

I needed a function which is similar to Array#select, but passes to the block not only the data item, but also the index of the element (similar to Enumerable#each_with_index). I tried this:

class Array  
  def select_with_index
    self.each_with_index.select {|*args| yield(*args)}.map(&:first)
  end
end

This seems to work indeed:

['a','b','a','a','c'].select_with_index {|letter,index| letter == 'a' && index > 1 }

results in

["a", "a"]

as expected. However, what I don't like with my solution is, that one has to supply a block. Similar methods in the Ruby core can be called without a block and yield an Enumerator. How can I do this? I know that I can use block_given? to test for the presence of the block, but how do I continue then? Do I need a Fiber?

BTW, the code should work with both Ruby 1.9.3. and 2.x.

like image 445
user1934428 Avatar asked Mar 01 '26 20:03

user1934428


2 Answers

class Array  
  def select_with_index
    #      ⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓⇓  this
    return enum_for(:select_with_index) unless block_given?
    each_with_index.select {|*args| yield(*args)}.map(&:first)
  end
end
like image 194
Aleksei Matiushkin Avatar answered Mar 04 '26 11:03

Aleksei Matiushkin


You don't need your own method. You can leverage the fact that select returns an enumerator if called without a block, so you can just slap a with_index on top:

p ['a','b','a','a','c'].select.with_index {|letter,index| letter == 'a' && index > 1 }
# >> ["a", "a"]

If you really want this to be a method on array (for dynamic invocation and whatnot), this is trivial:

class Array
  def select_with_index(&block)
    select.with_index(&block)
  end
end

p ['a','b','a','a','c'].select_with_index {|letter,index| letter == 'a' && index > 1 }  
p ['a','b','a','a','c'].select_with_index
# >> ["a", "a"]
# >> #<Enumerator: #<Enumerator: ["a", "b", "a", "a", "c"]:select>:with_index>
like image 45
Sergio Tulentsev Avatar answered Mar 04 '26 12:03

Sergio Tulentsev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!