1

In the following situation:

xxx.delete_if do |x|
  yyy.descend do |y| # This is a pathname.descend
    zzz.each do |z|
      if x + y == z
        # Do something

        # Break all nested loops returning to "xxx.delete_if do |x|" loop

        # The "xxx.delete_if do |x|" must receive a "true" so that it
        # can delete the array item
      end
    end
  end
end

What is the best way to achieve this multiple nested break while making sure I can pass the true value so that the array item is deleted?

Maybe I should use multiple break statements that return true or use a throw/catch with a variable, but I don't know if those are the best answer.


This question is different from How to break from nested loops in Ruby? because it requires that the parent loop receives a value from the nested loop.

  • 3
    When someone makes a question that can easily be found by a Google search it receives many upvotes, but when it's something very specific and hard to find like this one it's downvoted. –  Aug 19 '16 at 17:56

2 Answers2

2

throw/catch (NOT raise/rescue) is the way I typically see this done.

xxx.delete_if do |x|
  catch(:done) do
    yyy.each do |y|
      zzz.each do |z|
        if x + y == z
          # Do something

          throw(:done, true)
        end
      end
    end
    false
  end
end

In fact, the Pickaxe explicitly recommends it:

While the exception mechanism of raise and rescue is great for abandoning execution when things go wrong, it's sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy. When Ruby encounters a throw, it zips back up the call stack looking for a catch block with a matching symbol. When it finds it, Ruby unwinds the stack to that point and terminates the block. If the throw is called with the optional second parameter, that value is returned as the value of the catch.

That said, max's #any? suggestion is a better fit for this problem.

Chris Heald
  • 61,439
  • 10
  • 123
  • 137
  • The problem is that `delete_if` requires a `true` to delete the array element. I tried your solution already before placing the question. –  Aug 19 '16 at 18:18
  • You didn't try it with the optional second parameter to `throw` then, because my solution works. :) – Chris Heald Aug 19 '16 at 18:21
  • I remember trying this too and it didn't work, since I tried many different solutions I will try it again and see if it works, maybe I did something wrong. –  Aug 19 '16 at 18:26
  • It does works well, I probably did something wrong, I selected the other answer however because it's shorter and I didn't notice any difference in performance. –  Aug 19 '16 at 22:08
1

You can use multiple break statements.

For example:

xxx.delete_if do |x|
  result = yyy.each do |y|
    result2 = zzz.each do |z|
      if x + y == z
         break true
      end
    end
    break true if result2 == true
  end
  result == true
end

However I would definitely avoid this in your particular situation.

You shouldn't be assigning variables to the result of each. Use map, reduce, select, reject, any?, all?, etc. instead

It makes more sense to use any? to accomplish the same purpose:

xxx.delete_if do |x|
  yyy.any? do |y|
    zzz.any? do |z|
      x + y == z
    end
  end
end
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • I didn't tough of using `any?` and it works fine, but in my specific case, one of the loops is a `path.descend` so it does require using a variable. I will update the question with the `descend`. –  Aug 19 '16 at 20:23
  • Actually there's a way, in my case, to don't use a variable, I just switched the last two loops, so the last one is the `yyy.descend do |y|`, this way I just have to add a `break true` inside it and everything works. Thanks. –  Aug 19 '16 at 21:54