0

I'm amazed with Ruby's syntax, I can only describe it in one word: comfortable.

EDIT: I think I wasn't clear. I want an easy way to exit loops with conditions.

Sadly, I can't find how to do this Java code in Ruby:

Assume: array = [1,2,3,4] array2 = [1,2,3,4]

boolean condition = false;
for(int i = 0; i < array.length && !condition; i++)
{
  for(int j = 0; j < array2.length && !condition; j++)
  {
     condition = (array[i] + array2[j] + 1 == 7);
  }
}

if(condition)
{
  System.out.println("Two elements of the arrays + 1 sum 7")
}

I love Ruby's one liners... But I can't even do this with full open loops...

I'm looking for something like this (each_while is made up):

array.each_while(condition && condition2) { SomeAction }

Which is the simplest way to do this in Ruby?

Most of the loops I work with have exiting conditions to optimize them. Everything I find on the internet is not acceptable for Ruby's beautiful syntax because it is even worse than Java, and we all know Java ain't pretty.

Some solution I found in the web:

catch "BreakOuterLoop" do
  for i in 1..10
    print "out #{i}\n"
    for j in 1..10
      print "in #{j}\n"
      throw "BreakOuterLoop" if i+j > 16
    end
  end
end

Just awful...

AFP_555
  • 2,392
  • 4
  • 25
  • 45

4 Answers4

3

Loop with condition

You could use break :

array1.each do |x|
  break unless condition && condition2
    array2.each do |y|
      break unless condition3
      # do_something
    end
  end
end

If you need the indices in your conditions :

array1.each_with_index do |x,i|
  break unless condition && condition2
    array2.each_with_index do |y,j|
      break unless condition3
      # do_something
    end
  end
end

Specific problem

Boolean

For your updated problem, you can use any?. It is exactly what you wanted to do. It iterates as long as a condition isn't true, and returns a value ASAP :

array  = [1,2,3,4]
array2 = [1,2,3,4]

puts array.product(array2).any?{|a,b| a + b + 1 == 7 }
#=> true

Or :

puts array.any?{|a| array2.any?{ |b| a + b + 1 == 7 } }
#=> true

puts array.any?{|a| array2.any?{ |b| a + b + 1 == 12 } }
#=> false

The second example should be faster, because not every pair is created : As soon as one is found, true is returned.

Pair

If you want to know for which pair the condition is true, you can use find:

p array.product(array2).find { |a, b| a + b + 1 == 7 }
#=> [2,4]

p array.product(array2).find { |a, b| a + b + 1 == 12 }
#=> nil

Optimization for huge arrays

The above code will run slow for huge arrays.

You could convert the biggest array to a Set, and use a direct lookup :

require 'set'

array  = [1, 2, 3, 4]
array2 = [1, 2, 3, 4]
set2 = array2.to_set

sum = 7 - 1
x1 = array.find { |x| set2.include?(sum - x) }

if x1
  puts "#{x1} + #{sum - x1} + 1 = #{sum + 1}"
end

#=> 2 + 4 + 1 = 7
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • 2
    Wasting billions of dummy iterations on huge array and always wrong conditions :) – Aleksei Matiushkin Jan 12 '17 at 10:16
  • Yeha, but it is so ugly... What if I have three nested loops and every loop has conditions? – AFP_555 Jan 12 '17 at 10:16
  • @mudasobwa Updated it. Thanks for the comment. – Eric Duminil Jan 12 '17 at 10:22
  • Ok, I'm checking this answer. I think this is the closest this will get to decent code. Kinda dissapointed in Ruby... Conditioned iterations aren't rare. – AFP_555 Jan 12 '17 at 10:29
  • It would be very nice if they implemented the each_while method I proposed. – AFP_555 Jan 12 '17 at 10:31
  • 2
    It's too early to be disappointed in Ruby. Just like you can write FORTRAN in any language, if you write Java code in Ruby, it feels kinda wrong. Please post a concrete question with data and logic, and you'll get blown away by what Enumerables can do. – Eric Duminil Jan 12 '17 at 10:33
  • @AFP_555 please see my answer part 2. `each_while` is in fact implemented, since one might stack iterators. – Aleksei Matiushkin Jan 12 '17 at 10:34
  • `.each_while(condition && condition2)` cannot work, because `condition && condition2` would be interpreted only once, before the loop happens. It would need to be in a block. – Eric Duminil Jan 12 '17 at 10:34
  • @AFP_555, I really don't understand this. There is an `each_while` method in ruby, it's called `take_while` xd – ndnenkov Jan 12 '17 at 10:34
  • @ndn Yes, but the take_while method works while the iterated elements fullfil a condition. Not while some other booleans or variables change values. – AFP_555 Jan 12 '17 at 10:36
  • @Eric Duminil You are completely right. It wouldn't work. It would just pass the value as parameters and would not be evaluated for each iteration. – AFP_555 Jan 12 '17 at 10:36
  • @AFP_555, not true. You can use whatever booleans or conditions in `take_while`, it just generally doesn't make much sense not to use the current element. – ndnenkov Jan 12 '17 at 10:37
  • @ndn. I was talking about AFP_555 proposal to put the conditions as parameters. – Eric Duminil Jan 12 '17 at 10:38
  • @ndn I edited the logic. If this can be done with take_while, please answer and I'll check it. That would be the best solution. – AFP_555 Jan 12 '17 at 10:47
  • Hmmm ok... I think this question IS too broad. I guess for each case there is a specific solution. I was looking for a general way to solve this conditioned loops. You solved the updated version operating two arrays with "any" to search for something, which doesn't apply to every conditioned loop. Thanks very much for the insight. – AFP_555 Jan 12 '17 at 10:54
  • @AFP_555 basically, you want to map an ugly (iterative) pattern to a pretty (functional) pattern. You can still do that (I will add a generic solution when I get back from lunch), but the fact still stands - any specific use case that you can think of has a prettier specific functional solution. – ndnenkov Jan 12 '17 at 11:01
  • `find` and `any?` should already cover a lot of "conditioned loop" cases. – Eric Duminil Jan 12 '17 at 11:02
  • @ndn Yes, I guess there will always be specific beautiful and faster ways. But those require knowledge... So it would be nice to have a general way to rely on while I learn all the specifics. I'm still a Java 7 guy, even things like 'any?' amaze me. – AFP_555 Jan 12 '17 at 11:25
  • @AFP_555: I tried to summarize some Enumerable methods here : http://stackoverflow.com/questions/40469476/ruby-choosing-between-each-map-inject-each-with-index-and-each-with-object – Eric Duminil Jan 12 '17 at 12:38
  • @AFP_555 : I updated the answer with a much faster solution. – Eric Duminil Jan 12 '17 at 12:47
  • @EricDuminil Thanks, I started learning yesterday and that post really helped me. Also, it's a good thing I already got to see all those creative solutions for a problem with many different tools. Hands down, I was wrong, Ruby rocks. I would like to say Ruby > Java, but I haven't reached the 'enterpisy' face of Ruby. – AFP_555 Jan 12 '17 at 21:45
  • `Ruby <=> Java` isn't defined. They're both great tools for different needs. I enjoy both. Java sucks without a good IDE, but with Eclipse or Netbeans it can be enjoyable. – Eric Duminil Jan 12 '17 at 21:48
3
require 'matrix'

Matrix[0...rows, 0...cols].each_with_index do |e, row, col|
  next unless [cond1, cond2, cond3].reduce :&
  # code
end

array1.each.with_index.lazy.take_while { cond1 }.each do |e1, i1|
  array2.each.with_index.lazy.take_while { cond2 }.each do |e2, i2|
    # do some stuff
  end
end
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • A wise man once told me that I was "Wasting billions of dummy iterations on huge array and always wrong conditions" ;) – Eric Duminil Jan 12 '17 at 10:41
  • @EricDuminil bah :) this was written in an assumption conditions might change back and forth, in this case we have to iterate through everything. – Aleksei Matiushkin Jan 12 '17 at 10:46
  • Ok. THIS (the second aproach) is what I was looking for. This is a general way to solve a conditioned for loop. – AFP_555 Jan 12 '17 at 11:12
  • @mudasobwa: You could add `p :condition1` and `p [e1,i1]` inside the blocks to show that they're interleaved. – Eric Duminil Jan 12 '17 at 12:53
0
array.length.times do |i|
  break unless condition_1 && condition_2
  # some action
end

break will stop the loop as soon as the conditions are no longer met

Regarding the loop matrix

Of course you can nest a loop within a loops within a loop, but that is definitely not the ruby way. I'm sure there is a nicer solution to whatever problem may arise.

davegson
  • 8,205
  • 4
  • 51
  • 71
0

As stated before, there is a prettier functional solution for probably any specific use case you can think of. I will try to answer the more general question, which is how to convert this java code:

int somethingYouWillMutate = 0; // any types or values or number of things
for(int i = 0; i < array.length && condition && condition2; i++) {
  for(int j = 0; j < array2.length && condition; j++) {
     someAction(array[i], array2[j], somethingYouWillMutate);
  }
}

To ruby:

something_you_will_mutate = 0
array.product(array2).each do |e1, e2|
  break unless condition && condition2

  some_action(e1, e2)
end

Note:

while c1 && c2 =:=
while true; break if !(c1 && c2) =:=
while true; break unless c1 && c2

If you want the indices as well:

array_indexed  = array.each_with_index.to_a
array2_indexed = array2.each_with_index.to_a

array_indexed.product(array2_indexed).each do |(e1, i1), (e2, i2)|
  break unless condition && condition2

  some_action(e1, e2, i1, i2, something_you_will_mutate)
end

Note: If you want an even more generic solution (with 3 or more arrays for example):

[array, array2, array3...].
  map(&:each_with_index).
  map(&:to_a).
  reduce(:product).
  map(&:flatten).
  each do |e1, i1, e2, i2, e3, i3...|
    break unless condition && condition2 && condition3...

    some_action(e1, e2, i1, i2, e3, i3..., something_you_will_mutate)
  end
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • This doesn't work because the conditions would be evaluated previous to the "each" iteration. What if the condition depends on the actions inside the "each" iteration? – AFP_555 Jan 12 '17 at 10:21
  • @ndn: I don't think so, because `_whatever_will_be_needed_in_both` might depend on `_co‌​ntinue_the_action_`. You'd also need `lazy` somewhere to avoid using `map` on unneeded elements. – Eric Duminil Jan 12 '17 at 10:26
  • @EricDuminil, for the first point - it can't, that is the reason they are called like that. For the second point - indeed, lazy is needed. – ndnenkov Jan 12 '17 at 10:28
  • @ndn Hey, sorry. take_while did work with the lazy. I'm just too new to know about the lazy thing. – AFP_555 Jan 12 '17 at 11:12
  • @AFP_555, replaced my answer with something more generic. – ndnenkov Jan 12 '17 at 11:57