Есть ли в Ruby цикл "do ... while"?

453

Я использую этот код, чтобы позволить пользователю вводить имена, в то время как программа сохраняет их в массиве, пока они не введут пустую строку (они должны нажимать ввод после каждого имени):

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

Этот код будет выглядеть намного лучше в цикле do ... while:

people = []

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

В этом коде мне не нужно присваивать информацию какой-либо случайной строке.

К сожалению, этот тип цикла не существует в Ruby. Кто-нибудь может предложить лучший способ сделать это?

Джереми Рутен
источник
1
Я думаю, что обычный цикл while выглядит лучше и его легче читать.
Магне
1
@ Джереми Рутен, есть ли шанс, что вам будет интересно изменить принятый ответ на ответ Сивей Шен loop do; ...; break if ...; end?
Дэвид Винецки

Ответы:

645

ВНИМАНИЕ :

begin <code> end while <condition>Отвергается автором Руби Matz. Вместо этого он предлагает использовать Kernel#loop, например,

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

Вот обмен электронной почтой в 23 ноября 2005 года, где Мац заявляет:

|> 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 имеет похожую историю:

В ноябре 2005 года Юкихиро Мацумото, создатель Ruby, пожалел об этой функции цикла и предложил использовать Kernel # loop.

Сивей Шен 申思维
источник
18
Пятно на. Этот begin end whileметод не показался правильным. Спасибо, что дали мне корм, который мне нужен, чтобы убедить остальную команду.
Джошуа Пинтер
2
Похоже, что цикл begin-end-while действительно оценивает условие перед запуском цикла. Разница между этим и обычным циклом while заключается в том, что он гарантированно будет запущен хотя бы один раз. Это достаточно близко, чтобы сделать ... в то время как вызвать проблемы.
user1992284
4
Итак, насколько я понял хорошо, begin-end-while «сожалеет», потому что нарушает семантику модификаторов, то есть: они проверяются перед выполнением блока, например: put k if! K.nil ?. Здесь 'if' является 'модификатором': он проверяется ДО, чтобы определить, выполняется ли оператор 'put k'. Это не тот случай, когда циклы while / till это (при использовании в качестве модификаторов блока begin-end-block) !), оцениваются ПОСЛЕ первого исполнения. Может быть, это вызвало сожаление, но разве у нас есть что-то «более сильное», чем старая публикация на форуме, что-то вроде официального осуждения, как это было во многих других случаях?
AgostinoX
2
Мне потребовалось смущающее время, чтобы понять, почему мое использование этого рубинового цикла «пока-что» не работает. Вы должны использовать «если», чтобы более точно имитировать работу в стиле «с», в противном случае вы можете оказаться как я и забыть инвертировать условие: p
Коннор Кларк
1
@James По ссылке на почту, он сказал, что «сожалеет», что добавил ее. Иногда люди делают ошибки, даже если они дизайнеры языка.
Xiong Chiamiov
188

Я нашел следующий фрагмент при чтении исходного кода Tempfile#initializeв базовой библиотеке Ruby:

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)

На первый взгляд, я предполагал, что модификатор while будет оценен до содержимого begin ... end, но это не так. Заметим:

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

Как и следовало ожидать, цикл продолжит выполняться, пока модификатор имеет значение true.

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

Хотя я был бы счастлив, что никогда больше не увижу эту идиому, начни ... конец довольно мощный. Ниже приводится распространенная идиома для запоминания однострочного метода без параметров:

def expensive
  @expensive ||= 2 + 2
end

Вот уродливый, но быстрый способ запомнить что-то более сложное:

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

Первоначально написано Джереми Вурхис . Содержание было скопировано здесь, потому что, кажется, оно было удалено с исходного сайта. Копии также можно найти в веб-архиве и на форуме Ruby Buzz . -Билл Ящерица

hubbardr
источник
5
Предоставлено Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.com/…
2010 г.,
56
Вот почему, когда я ссылаюсь на внешний сайт, я всегда копирую соответствующую информацию в мой ответ здесь.
Давр
10
Почему вы ожидаете, что модификатор while будет оценен до того, как содержимое begin ... end? Вот так делай .. тогда как циклы должны работать. И почему бы вам "быть счастливым, чтобы никогда больше не увидеть эту идиому?" Что с этим не так? Я смущен.
bergie3000
2
начало ... конец выглядит как блок, аналогично {...}. Ничего плохого в этом нет.
Виктор Пудеев
1
-1: ответ Сивей Шен объясняет, что begin .. endэто несколько осуждается. Используйте loop do .. break if <condition>вместо этого.
Кевин
102

Нравится:

people = []

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

Ссылка: Ruby's Hidden do {} while () Loop

Blorgbeard отсутствует
источник
1
хех, избили это. черт.
Blorgbeard выходит
Не добавит ли этот код пустую строку в массив, если нет ввода?
AndrewR
1
Хотя здесь это не применимо, одна из проблем конструкции begin-end-while заключается в том, что, в отличие от всех других конструкций ruby, она не возвращает значение последнего выражения: «begin 1 end while false» возвращает nil (не 1, не ложь)
токланд
9
Вы можете использовать, until info.empty?а не while not info.empty?.
Эндрю Гримм
На самом деле @AndrewR - это своего рода точка ... чтобы сделать что-то перед сравнением .. сделать diplay ("Остаток = # {счет}), в то время как (счет> 0) ... Я получаю отображение" Осталось = 0 ".. идеально!
baash05
45

Как насчет этого?

people = []

until (info = gets.chomp).empty?
  people += [Person.new(info)]
end
AndrewR
источник
4
Красиво, намного элегантнее.
Blorgbeard отсутствует
23
Но это не цикл "делай ... пока". :)
Александр Прокофьев
4
Но он делает то же самое в этом случае, если я не ошибаюсь
Blorgbeard отсутствует
18
@Blorgbeard, цикл do.. while всегда запускается один раз, затем оценивает, должен ли он продолжать работать. Традиционный цикл while / till может выполняться 0 раз. Это не огромная разница, но они разные.
Скотт Суизи
5
@ Скотт, это правда - я просто имел в виду, что этот код эквивалентен ОП, хотя он и не использует do / while. Хотя на самом деле этот код выполняет половину «работы» цикла в условии, поэтому он также не совсем традиционный цикл while - если условие не совпадает, некоторая работа все еще выполняется.
Blorgbeard выходит
11

Вот полный текст статьи из мертвой ссылки Хаббардра на мой блог.

Я нашел следующий фрагмент при чтении исходного кода Tempfile#initializeв базовой библиотеке Ruby:

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)

На первый взгляд, я предположил, что whileмодификатор будет оценен перед содержимым begin...end, но это не так. Заметим:

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

Как и следовало ожидать, цикл продолжит выполняться, пока модификатор имеет значение true.

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

Хотя я был бы рад никогда больше не видеть эту идиому, begin...endона достаточно мощная. Ниже приводится распространенная идиома для запоминания однострочного метода без параметров:

def expensive
  @expensive ||= 2 + 2
end

Вот уродливый, но быстрый способ запомнить что-то более сложное:

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
jvoorhis
источник
10

Теперь это работает правильно:

begin
    # statment
end until <condition>

Но, это может быть удалено в будущем, потому что beginутверждение является нелогичным. Видеть: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745.

Matz (Ruby's Creator) рекомендовал сделать это следующим образом:

loop do
    # ...
    break if <condition>
end
Steely Wing
источник
6

Из того, что я собираюсь, Мац не нравится конструкция

begin
    <multiple_lines_of_code>
end while <cond>

потому что это семантика отличается от

<single_line_of_code> while <cond>

тем, что первая конструкция выполняет код в первую очередь перед проверкой условия, а вторая конструкция сначала проверяет условие перед тем, как выполнить код (если когда-либо). Я полагаю, что Матц предпочитает сохранять вторую конструкцию, потому что она соответствует одной строковой конструкции оператора if.

Мне никогда не нравилась вторая конструкция даже для операторов if. Во всех других случаях компьютер выполняет код слева направо (например, || и &&) сверху вниз. Люди читают код слева направо сверху вниз.

Вместо этого я предлагаю следующие конструкции:

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

Я не знаю, будут ли эти предложения разбираться с остальным языком. Но в любом случае я предпочитаю сохранять исполнение слева направо, а также согласованность языков.

JDS
источник
3
a = 1
while true
  puts a
  a += 1
  break if a > 10
end
Пол Гиллард
источник
3
это выглядит как goto. Код запутывает ваше намерение.
Герхард
Выглядит отлично для меня, за исключением while trueможет быть заменен на loop do.
Дэвид Винецки
@DavidWiniecki, действительно, while trueможно заменить на loop do. Но я протестировал обе конструкции с большим количеством итераций внутри цикла и обнаружил, что while trueэто как минимум в 2 раза быстрее, чем loop do. Не могу объяснить разницу, но она определенно есть. (Обнаружено во время тестирования Advent of Code 2017, день 15).
Jochem Schulenklopper
2

Вот еще один:

people = []
1.times do
  info = gets.chomp
  unless info.empty? 
    people += [Person.new(info)]
    redo
  end
end
Мурена
источник
Я предпочитаю это, так как unlessэто прямо перед собой, и я не читаю кучу кода (который может быть больше, чем показано здесь) только для того, чтобы найти «зависание» unlessв конце. В коде есть общий принцип, что модификатор и условия легче использовать, когда они «наперед», как это.
Майкл Даррант
Иногда мне бы хотелось, чтобы мы, программисты, платили наличными за каждое дополнительное сравнение. И то, как это «выглядит» для нас, было менее важно, чем то, как оно выглядит для того, кто использует его миллион раз в день.
baash05
-3
ppl = []
while (input=gets.chomp)
 if !input.empty?
  ppl << input
 else
 p ppl; puts "Goodbye"; break
 end
end
все хорошо
источник
2
это выглядит как goto. Код запутывает ваше намерение и выглядит очень непринужденно.
Герхард
запутывать * не запутывать.
QEDK