Is there a "do ... while" loop in Ruby?

RubyLoops

Ruby Problem Overview


I'm using this code to let the user enter in names while the program stores them in an array until they enter an empty string (they must press enter after each name):

people = []
info = 'a' # must fill variable with something, otherwise loop won't execute

while not info.empty?
	info = gets.chomp
	people += [Person.new(info)] if not info.empty?
end

This code would look much nicer in a do ... while loop:

people = []

do
    info = gets.chomp
    people += [Person.new(info)] if not info.empty?
while not info.empty?

In this code I don't have to assign info to some random string.

Unfortunately this type of loop doesn't seem to exist in Ruby. Can anybody suggest a better way of doing this?

Ruby Solutions


Solution 1 - Ruby

CAUTION:

The begin <code> end while <condition> is rejected by Ruby's author Matz. Instead he suggests using Kernel#loop, e.g.

loop do 
  # some code here
  break if <condition>
end 

Here's an email exchange in 23 Nov 2005 where Matz states:

|> Don't use it please.  I'm regretting this feature, and I'd like to
|> remove it in the future if it's possible.
|
|I'm surprised.  What do you regret about it?

Because it's hard for users to tell

  begin <code> end while <cond>

works differently from

  <code> while <cond>

RosettaCode wiki has a similar story: > During November 2005, Yukihiro Matsumoto, the creator of Ruby, regretted this loop feature and suggested using Kernel#loop.

Solution 2 - Ruby

> I found the following snippet while reading the source for Tempfile#initialize in the Ruby core library: > > begin > tmpname = File.join(tmpdir, make_tmpname(basename, n)) > lock = tmpname + '.lock' > n += 1 > end while @@cleanlist.include?(tmpname) or > File.exist?(lock) or File.exist?(tmpname) > > At first glance, I assumed the while modifier would be evaluated before the contents of begin...end, but that is not the case. Observe: > > >> begin > ?> puts "do {} while ()" > >> end while false > do {} while () > => nil > > As you would expect, the loop will continue to execute while the modifier is true. > > >> n = 3 > => 3 > >> begin > ?> puts n > >> n -= 1 > >> end while n > 0 > 3 > 2 > 1 > => nil > > While I would be happy to never see this idiom again, begin...end is quite powerful. The following is a common idiom to memoize a one-liner method with no params: > > def expensive > @expensive ||= 2 + 2 > end > > Here is an ugly, but quick way to memoize something more complex: > > def expensive > @expensive ||= > begin > n = 99 > buf = "" > begin > buf << "#{n} bottles of beer on the wall\n" > # ... > n -= 1 > end while n > 0 > buf << "no more bottles of beer" > end > end

Originally written by Jeremy Voorhis. The content has been copied here because it seems to have been taken down from the originating site. Copies can also be found in the Web Archive and at Ruby Buzz Forum. -Bill the Lizard

Solution 3 - Ruby

Like this:

people = []

begin
  info = gets.chomp
  people += [Person.new(info)] if not info.empty?
end while not info.empty?

Reference: Ruby's Hidden do {} while () Loop

Solution 4 - Ruby

How about this?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end

Solution 5 - Ruby

Here's the full text article from hubbardr's dead link to my blog.

I found the following snippet while reading the source for Tempfile#initialize in the Ruby core library:

begin
  tmpname = File.join(tmpdir, make_tmpname(basename, n))
  lock = tmpname + '.lock'
  n += 1
end while @@cleanlist.include?(tmpname) or
  File.exist?(lock) or File.exist?(tmpname)

At first glance, I assumed the while modifier would be evaluated before the contents of begin...end, but that is not the case. Observe:

>> begin
?>   puts "do {} while ()" 
>> end while false
do {} while ()
=> nil

As you would expect, the loop will continue to execute while the modifier is true.

>> n = 3
=> 3
>> begin
?>   puts n
>>   n -= 1
>> end while n > 0
3
2
1
=> nil

While I would be happy to never see this idiom again, begin...end is quite powerful. The following is a common idiom to memoize a one-liner method with no params:

def expensive
  @expensive ||= 2 + 2
end

Here is an ugly, but quick way to memoize something more complex:

def expensive
  @expensive ||=
    begin
      n = 99
      buf = "" 
      begin
        buf << "#{n} bottles of beer on the wall\n" 
        # ...
        n -= 1
      end while n > 0
      buf << "no more bottles of beer" 
    end
end

Solution 6 - Ruby

This works correctly now:

begin
    # statment
end until <condition>

But, it may be remove in the future, because the begin statement is counterintuitive. See: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Matz (Ruby’s Creator) recommended doing it this way:

loop do
    # ...
    break if <condition>
end

Solution 7 - Ruby

From what I gather, Matz does not like the construct

begin
	<multiple_lines_of_code>
end while <cond>

because, it's semantics is different than

<single_line_of_code> while <cond>

in that the first construct executes the code first before checking the condition, and the second construct tests the condition first before it executes the code (if ever). I take it Matz prefers to keep the second construct because it matches one line construct of if statements.

I never liked the second construct even for if statements. In all other cases, the computer executes code left-to-right (eg. || and &&) top-to-bottom. Humans read code left-to-right top-to-bottom.

I suggest the following constructs instead:

if <cond> then <one_line_code>		# matches case-when-then statement

while <cond> then <one_line_code>

<one_line_code> while <cond>

begin <multiple_line_code> end while <cond>	# or something similar but left-to-right

I don't know if those suggestions will parse with the rest of the language. But in any case I prefere keeping left-to-right execution as well as language consistency.

Solution 8 - Ruby

a = 1
while true
  puts a
  a += 1
  break if a > 10
end

Solution 9 - Ruby

Here's another one:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end

Solution 10 - Ruby

ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
      

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPaige RutenView Question on Stackoverflow
Solution 1 - RubySiweiView Answer on Stackoverflow
Solution 2 - RubyhubbardrView Answer on Stackoverflow
Solution 3 - RubyBlorgbeardView Answer on Stackoverflow
Solution 4 - RubyAndrewRView Answer on Stackoverflow
Solution 5 - RubyjvoorhisView Answer on Stackoverflow
Solution 6 - RubySteely WingView Answer on Stackoverflow
Solution 7 - RubyjdsView Answer on Stackoverflow
Solution 8 - RubyPaul GillardView Answer on Stackoverflow
Solution 9 - RubyMorayView Answer on Stackoverflow
Solution 10 - Rubyis_that_okayView Answer on Stackoverflow