When and Why use Loop Do Construct in Ruby
Asked Answered
T

5

10

I recently came up across a problem/solution that used Loop Do. I seldom have seen this so far in my learning Ruby Programming (I am a beginner with no CS experience).

# Write a function, `nearest_larger(arr, i)` which takes an array and an
# index.  The function should return another index, `j`: this should
# satisfy:
#
# (a) `arr[i] < arr[j]`, AND
# (b) there is no `j2` closer to `i` than `j` where `arr[i] < arr[j]`.
#
# In case of ties (see example beow), choose the earliest (left-most)
# of the two indices. If no number in `arr` is largr than `arr[i]`,
# return `nil`.
#
# Difficulty: 2/5

describe "#nearest_larger" do
  it "handles a simple case to the right" do
    nearest_larger([2,3,4,8], 2).should == 3
  end

  it "handles a simple case to the left" do
    nearest_larger([2,8,4,3], 2).should == 1
  end

  it "treats any two larger numbers like a tie" do
    nearest_larger([2,6,4,8], 2).should == 1
  end

  it "should choose the left case in a tie" do
    nearest_larger([2,6,4,6], 2).should == 1
  end

  it "handles a case with an answer > 1 distance to the left" do
    nearest_larger([8,2,4,3], 2).should == 0
  end

  it "handles a case with an answer > 1 distance to the right" do
    nearest_larger([2,4,3,8], 1).should == 3
  end

  it "should return nil if no larger number is found" do
    nearest_larger( [2, 6, 4, 8], 3).should == nil
  end
end

SOLUTION

def nearest_larger(arr, idx)
  diff = 1
  loop do
    left = idx - diff
    right = idx + diff

    if (left >= 0) && (arr[left] > arr[idx])
      return left
    elsif (right < arr.length) && (arr[right] > arr[idx])
      return right
    elsif (left < 0) && (right >= arr.length)
      return nil
    end

    diff += 1
  end
end
 nearest_larger([2,4,3,8], 1)

Can someone please explain to me when is the best time to use a "loop do" construct instead of the usual "while" or "unless" or "each" construct?

Tramway answered 29/5, 2013 at 11:55 Comment(1)
The simple answer is “rarely”. I’d only use it if I want a code to run indefinitely, until it’s stopped manually or by some other process. I’ve had scripts to poll a table, to act on new rows, and I’ve used it for generative music scripts. For both I’d have a ‘sleep’ call to regulate the amount of work it does. For both it continues until I manually stop it.Isidora
M
18

In a language without loop, you might use a while construct like:

while( true ) {
  # Do stuff until you detect it is done
  if (done) break;
}

The point of it is that you start the loop without knowing how many of iterations to perform (or it is hard to calculate in advance), but it is easy to detect when the loop should end. In addition, for a particular case you might find the equivalent while (! done) { # do stuff } syntax clumsy, because the done condition can happen halfway through the loop, or in multiple places.

Ruby's loop is basically the same thing as the while( true ) - in fact you can use while( true ) almost interchangeably with it.

In the given example, there are following points of return within each iteration:

if (left >= 0) && (arr[left] > arr[idx])
  return left   # <-- HERE
elsif (right < arr.length) && (arr[right] > arr[idx])
  return right  # <-- HERE
elsif (left < 0) && (right >= arr.length)
  return nil    # <-- HERE
end 

There is also an implied "else continue looping" here, if no end conditions are met.

These multiple possible exit points are presumably why the author chose the loop construct, although there are many ways of solving this problem in practice with Ruby. The given solution code is not necessarily superior to all other possibilities.

Marymarya answered 29/5, 2013 at 12:8 Comment(3)
you only use a while when you don't know when it will terminate.Tutor
@Woot4Moo: Good point. It is difficult to differentiate why you might choose between while (condition) {} and while(1) { if (condition) break; } syntax in a short answer.Marymarya
indeed :), i do like there until construct as well.Tutor
G
28

Adding up to the previous answers,

The "loop do" construct also offers a cleaner syntax when working with external iterators, e.g

No "loop do"

my_iterator = (1..9).each
begin
  while(true)
    puts my_iterator.next
  end
rescue StopIteration => e
  puts e
end

And now with "loop do" this would become

my_iterator = (1..9).each
loop do
  puts my_iterator.next
end

And the exception is handled for you. It also allows you to loop through two collections at the same time and as soon as one of them runs out of elements the loop exits gracefully,

iterator = (1..9).each
iterator_two = (1..5).each

loop do
  puts iterator.next
  puts iterator_two.next
end

It will print: 1,1,2,2,3,3,4,4,5,5,6.

More info on it at: ruby-docs.org

Greenlet answered 10/10, 2013 at 4:11 Comment(0)
M
18

In a language without loop, you might use a while construct like:

while( true ) {
  # Do stuff until you detect it is done
  if (done) break;
}

The point of it is that you start the loop without knowing how many of iterations to perform (or it is hard to calculate in advance), but it is easy to detect when the loop should end. In addition, for a particular case you might find the equivalent while (! done) { # do stuff } syntax clumsy, because the done condition can happen halfway through the loop, or in multiple places.

Ruby's loop is basically the same thing as the while( true ) - in fact you can use while( true ) almost interchangeably with it.

In the given example, there are following points of return within each iteration:

if (left >= 0) && (arr[left] > arr[idx])
  return left   # <-- HERE
elsif (right < arr.length) && (arr[right] > arr[idx])
  return right  # <-- HERE
elsif (left < 0) && (right >= arr.length)
  return nil    # <-- HERE
end 

There is also an implied "else continue looping" here, if no end conditions are met.

These multiple possible exit points are presumably why the author chose the loop construct, although there are many ways of solving this problem in practice with Ruby. The given solution code is not necessarily superior to all other possibilities.

Marymarya answered 29/5, 2013 at 12:8 Comment(3)
you only use a while when you don't know when it will terminate.Tutor
@Woot4Moo: Good point. It is difficult to differentiate why you might choose between while (condition) {} and while(1) { if (condition) break; } syntax in a short answer.Marymarya
indeed :), i do like there until construct as well.Tutor
T
4

Using the loop do construct allows you to break on a conditional.

for instance:

i=0
loop do
  i+=1
  print "#{i} "
  break if i==10
end 

You would want to use this when you know the number of elements that will be processed, similar to that of the for each loop

Tutor answered 29/5, 2013 at 11:58 Comment(5)
Would using the "until" construct be the same as that?Tramway
@JamieS until is the opposite of while insofar as you are looking for the negation of the variable being referenced by until. That is: while(true) vice until falseTutor
You can break from within any loop, not just a loop do.Athey
@Athey did someone say you couldnt?Tutor
what does your answer add? you're just explaining loop do.Mccaffrey
S
2

loop with 'loop' construct will execute the given block endlessly until the code inside the block breaks on certain condition.

it can be used when you don't have a collection to loop over, the places where 'each' and 'for' cannot work.

the different between 'loop' and while/until is that while/until will execute the given block when certain condition is meet, where as in case of loop there is no condition to start, condition lies inside the loop's block.

for better understanding read doc.

http://www.ruby-doc.org/core-1.9.2/Kernel.html#method-i-loop

Shelf answered 29/5, 2013 at 12:28 Comment(0)
M
0

Suppose You wanted to put a number of conditions, it might be neater to put them together. Instead of this, for example:

x = 0
while x <= 10
    num = gets.to_f

    break if num < 1
    break if /\D/.match? num.to_s

    puts num ** 2
end

Grouping the breaks together makes it more readable

x = 0
loop do
    num = gets.to_f

    break if num < 1
    break if x <= 10
    break if /\D/.match? num.to_s

    puts num ** 2
end
Mccaffrey answered 12/9, 2019 at 11:41 Comment(1)
x=0; while true; num = gets; break if ...; puts num.to_f ** 2; endHomocercal

© 2022 - 2024 — McMap. All rights reserved.