user1934428
user1934428

Reputation: 22225

Ruby: Combining enumerators, making a new enumerator

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.

Upvotes: 2

Views: 148

Answers (2)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230296

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>

Upvotes: 2

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

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

Upvotes: 3

Related Questions