2
\$\begingroup\$

I have an array of some elements that I need to loop through (i.e. execute some block A on each element) but between them I have also to "clean up" (block B) from the previous iteration and that I don't need to clean after the last iteration because B is slow and it is done somewhere further in more efficient way like process exit or whatever. If the array is [1,2,3] the flow should be like:

[1, :IN]
[1, :OUT]
[2, :IN]
[2, :OUT]
[3, :IN]

It is similar to Mathematica Riffle[] function. I already implemented it in Ruby three years ago but that implementation is complex and there is a place only for B, not for A. So I rewrote it for my current case:

riffle = lambda do |enumerable, &block|
  prev = nil
  enumerable.each_with_index do |e, i|
    block.call prev unless i.zero?
    prev = e
  end
end

riffle.call( Enumerator.new do |e|
  [1,2,3].each do |i|
    e << i

    # start block A
    p [i, :IN]
    # end

  end
end ) do |i|

  # start block B
  p [i, :OUT]    
  # end

end

Is there a way to make less code and maybe not use Enumerator.new? I tried to use .to_enum and .lazy.map(&A) but had no success.

\$\endgroup\$
1
\$\begingroup\$

There's not too much to say about your implementation, though I'm not sure declaring riffle as a lambda provides any benefit instead of just defining it as a standard function.

Instead of storing the previous element of the iteration and using it to call the block, you should check only call the block if you are not on the last element of the enumerable (if possible, of course, there are some cases when it isn't).

While you can use a mixture of a custom enumerator and a block, it's not the nicest way to do things. I'd recommend instead passing in two lambdas (or a lambda and a block) so that it is immediately clear that the first block follows the second.

def riffle(enumerable, a, b)
  enumerable.each_with_index  do |x, i|
    a.call x
    b.call x unless i + 1 == enumerable.size
  end
end

riffle [1, 2, 3],
  -> i { p [i, :IN] },
  -> i { p [i, :OUT] }

This gives the output:

[1, :IN]
[1, :OUT]
[2, :IN]
[2, :OUT]
[3, :IN]
=> [1, 2, 3]
\$\endgroup\$
  • \$\begingroup\$ It is a habit to use lambda instead of def whenever possible to avoid outer scope pollution and have a benefit of access to local variables if needed. Seems like using lambda+lambda instead of Enumerator+block looks clearer inside because you call both in the same way -- with .call. Have an upvote, I'll accept later. \$\endgroup\$ – Nakilon Apr 3 at 9:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.