8

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?

JaTo
  • 2,742
  • 4
  • 29
  • 38
  • 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. – AJFaraday Feb 18 '20 at 21:56

5 Answers5

26

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

radyz
  • 1,664
  • 1
  • 17
  • 26
16

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.

Neil Slater
  • 26,512
  • 6
  • 76
  • 94
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

Woot4Moo
  • 23,987
  • 16
  • 94
  • 151
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

Substantial
  • 6,684
  • 2
  • 31
  • 40
Sachin Singh
  • 7,107
  • 6
  • 40
  • 80
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
Sapphire_Brick
  • 1,560
  • 12
  • 26