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

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

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 не существует. Кто-нибудь может предложить лучший способ сделать это?

Я думаю, что обычный цикл while выглядит лучше и его легче читать.

Magne 14.11.2013 14:57

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

David Winiecki 04.05.2014 02:08
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
462
2
262 321
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

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

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

Предоставлено Wayback Machine: web.archive.org/web/20080206125158/http://archive.jvoorhis.c‌ om /…

bta 22.07.2010 19:32

Вот почему, ссылаясь на внешний сайт, я всегда стараюсь скопировать соответствующую информацию в свой ответ здесь.

davr 09.09.2010 04:03

Почему вы ожидаете, что модификатор while будет оцениваться до содержимого begin ... end? Так делают .. циклы пока должны работать. И с чего бы вам «быть счастливым, никогда больше не увидеть эту идиому»? Что с этим не так? Я запутался.

bergie3000 13.02.2013 23:33

begin ... end выглядит как блок, аналогично {...}. В этом нет ничего плохого.

Victor Pudeyev 08.06.2013 09:22

-1: Ответ Сивэй Шена объясняет, что begin .. end несколько осуждается. Вместо этого используйте loop do .. break if <condition>.

Kevin 18.10.2013 23:27

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

Fedcomp 21.07.2015 11:10

@Fedcomp извините, по сравнению с другими языками является довольно спорным. Дело в том, что Ruby несовместим с самим собой, поэтому разработчик языка сожалеет об этом. Я расширил суть, чтобы уточнить, что означает "несогласованный" gist.github.com/choonkeat/9815130

choonkeat 22.07.2015 06:32

Довольно мило, как вы набрали 176 голосов за ответ Джереми Вурхиса!

Jacob 02.04.2016 00:39

@ Джейкоб, я знаю: | Изначально это была просто ссылка, но, как отмечают комментаторы, она была удалена, поэтому редактор изменил сообщение и скопировал его содержимое. Это не то, что я изначально опубликовал. :)

hubbardr 02.04.2016 02:20

Нравится:

people = []

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

Ссылка: Скрытый цикл Ruby do {} while ()

Не добавит ли этот код пустую строку в массив, если нет ввода?

AndrewR 26.09.2008 03:25

Хотя здесь это не применяется, одна проблема конструкции begin-end-while заключается в том, что, в отличие от всех других конструкций ruby, она не возвращает значение последнего выражения: «begin 1 end while false» возвращает nil (не 1, не ложь)

tokland 23.12.2010 15:03

Вы можете использовать until info.empty?, а не while not info.empty?.

Andrew Grimm 02.08.2011 06:56

На самом деле @AndrewR это своего рода точка ... сделать что-то перед сравнением ... отобразить ("Остающийся = # {счетчик}) пока (счетчик> 0) ... Я получаю отображение" Осталось = 0 ".. идеально!

baash05 17.12.2012 02:02

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

people = []

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

Но это не цикл «делать ... пока». :)

Alexander Prokofyev 26.09.2008 08:50

Но то же самое и в этом случае, если я не ошибаюсь

Blorgbeard 30.09.2008 11:25

@Blorgbeard, цикл do..time всегда выполняется один раз, а затем оценивает, следует ли ему продолжать работу. Традиционный цикл while / until может выполняться 0 раз. Это не ОГРОМНАЯ разница, но они разные.

Scott Swezey 07.01.2010 05:31

@Scott, это правда - я просто имел в виду, что этот код эквивалентен OP, даже если он не использует do / while. Хотя на самом деле этот код выполняет половину «работы» цикла в условии, так что это тоже не совсем традиционный цикл while - если условие не совпадает, некоторая работа все еще выполняется.

Blorgbeard 14.04.2010 01:13

Просто попробовал это. У вас может быть блок в форме начинаться ... заканчиваться до. Здорово!

Venkat D. 18.04.2011 08:03

Это цикл while, а не do ... while.

davorb 19.08.2011 07:22

Нет цикла do while ... это простое время do.

baash05 17.12.2012 02:11

Вопрос был в том, может ли кто-нибудь предложить лучший способ написания кода, который не обязательно требует времени. Поскольку это элегантно отвечает на вопрос, +1.

Eva 14.02.2013 08:20

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

это немного похоже на goto. Код скрывает ваше намерение и выглядит очень неубедительным.

Gerhard 25.11.2010 16:16

запутывать * не запутывать.

qedk 27.05.2015 18:19

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

При чтении исходного кода 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

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

>> 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

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

это немного похоже на переход к. Код скрывает ваше намерение.

Gerhard 25.11.2010 16:14

Выглядит отлично, за исключением того, что while true можно заменить на loop do.

David Winiecki 04.05.2014 02:14

@DavidWiniecki, действительно, while true можно заменить на loop do. Но я протестировал обе конструкции с большим количеством итераций внутри цикла и обнаружил, что while true как минимум в 2 раза больше Быстрее, чем loop do. Не могу объяснить разницу, но она определенно есть. (Обнаружено во время тестирования Advent of Code 2017, день 15.)

Jochem Schulenklopper 16.12.2017 00:34

Вот еще один:

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

Я предпочитаю это, так как unless находится прямо впереди, и я не читаю кучу кода (которого может быть больше, чем показано здесь) только для того, чтобы найти «болтающийся» unless в конце. Общий принцип кода состоит в том, что модификаторы и условия легче использовать, если они расположены так «впереди».

Michael Durrant 08.03.2012 22:05

Иногда мне хочется, чтобы мы, программисты, платили наличными за каждое дополнительное сравнение. И то, как он «выглядит» для нас, менее важно, чем то, как он выглядит для того, что использует его миллион раз в день.

baash05 17.12.2012 02:15
Ответ принят как подходящий

ОСТОРОЖНОСТЬ:

begin <code> end while <condition> отклонен автором Ruby Матцем. Вместо этого он предлагает использовать 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 вики похожая история:

During November 2005, Yukihiro Matsumoto, the creator of Ruby, regretted this loop feature and suggested using Kernel#loop.

Пятно на. Этот метод begin end while казался неправильным. Спасибо, что дали мне корм, в котором я нуждался, чтобы убедить остальную команду.

Joshua Pinter 26.11.2012 02:39

Похоже, что цикл begin-end-while фактически оценивает условие перед запуском цикла. Разница между этим и обычным циклом while заключается в том, что он гарантированно запускается хотя бы один раз. Этого достаточно, чтобы сделать ... и вызвать проблемы.

user1992284 12.06.2013 13:54

Итак, насколько я хорошо понял, begin-end-while «сожалеет», потому что нарушает семантику модификаторов, то есть: они проверяются перед выполнением блока, например: кладет k if! K.nil ?. Здесь if - это модификатор: он проверяется ПЕРЕД, чтобы определить, выполняется ли выполнение оператора put k. Это не тот случай, когда while / until циклически повторяется (при использовании в качестве модификаторов блока begin-end !), оцениваются ПОСЛЕ первого выполнения. Может быть, это вызвало сожаление, но есть ли у нас что-то «сильнее», чем старый пост на форуме, своего рода официальное осуждение, как это обычно бывает во многих других случаях?

AgostinoX 02.01.2015 20:40

Это объяснение, которое я искал, когда задавался вопросом, почему для этого есть проверка Rubocop (Lint / Loop) github.com/bbatsov/rubocop/blob/v0.34.2/lib/rubocop/cop/lint‌ /…

Rob 29.10.2015 22:04

Мне потребовалось унизительное количество времени, чтобы понять, почему я не использовал этот рубиновый цикл do-while. Вы должны использовать 'if', чтобы более точно имитировать do-while в стиле c, иначе вы можете закончить, как я, и забыть инвертировать условие: p

Connor Clark 18.12.2015 00:31

В моих Ruby 2.2.3 и 1.9.3 состояние while оценивается после выполнения цикла begin-end. Посмотрите этот фрагмент кода: repl.it/CDJE/0

pablo 07.04.2016 14:34

Я не понимаю, зачем ему добавлять его в язык, если он не согласен с его использованием?

James 02.10.2018 14:21

@James Согласно связанному письму, он сказал, что "сожалеет" о его добавлении. Иногда люди совершают ошибки, даже если они разработчики языков.

Xiong Chiamiov 20.02.2019 21:54

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

begin
    # statment
end until <condition>

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

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

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

Насколько я понимаю, Мацу не нравится конструкция

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

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

Другие вопросы по теме