Чи є цикл "робити ... поки" в Ruby?


454

Я використовую цей код, щоб дозволити користувачеві вводити імена, поки програма зберігає їх у масиві, поки він не введе порожній рядок (вони повинні натиснути клавішу enter після кожного імені):

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

Цей код буде виглядати набагато приємніше у виконанні ... циклу:

people = []

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

У цьому коді мені не потрібно присвоювати інформацію якомусь випадковому рядку.

На жаль, подібний цикл, схоже, не існує в Ruby. Хтось може запропонувати кращий спосіб зробити це?


1
Я думаю, що нормальний цикл виглядає приємніше, і його легше читати.
Магне

1
@Jeremy Ruten Чи є якийсь шанс, що вам було б цікаво змінити прийняту відповідь на відповідь Сівей Шен 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 мають схожу історію:

У листопаді 2005 року Юкіхіро Мацумото, творець Ruby, пошкодував цю функцію циклу і запропонував використовувати цикл Kernel #.


18
Пляма на. Цей begin end whileметод не здався правильним. Дякую за те, що мені дали корм, який мені знадобився, щоб переконати решту команди.
Джошуа Пінтер

2
Схоже, цикл початку-кінця-час насправді оцінює стан перед запуском циклу. Різниця між цим та звичайним циклом у той час, що гарантовано запустити хоча б один раз. Це досить близько, щоб зробити ... в той час, як викликати проблеми.
користувач1992284

4
Отже, наскільки я добре зрозумів, start-end-while "шкодується", оскільки порушує семантику модифікаторів, тобто: вони перевіряються перед виконанням блоку, наприклад: ставить k if! K.nil ?. Тут 'if' є 'модифікатором': він перевіряється ДО ПЕРЕД, щоб визначити, чи виконує оператор 'ставить k'. Це не той час, поки циклі / до тих пір, коли (коли вони використовуються як модифікатори блоку початку-кінця !), оцінюються ПІСЛЯ першої страти. Можливо, це викликало жаль, але чи є у нас щось «сильніше», ніж старе повідомлення на форумі, якесь офіційне зневаження, як це буває у багатьох інших випадках?
AgostinoX

2
Мені було потрібно ніяково багато часу, щоб зрозуміти, чому моє використання цього рубінового циклу "до часу" не працює. Вам слід використовувати "за винятком", щоб більш чітко імітувати до-час у стилі c, інакше ви можете закінчитись так, як я, і забути перевернути умову: p
Connor Clark

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

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

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 Forum . -Забери ящірку


5
Люб’язність зворотної
bta

56
Ось чому, при посиланні на зовнішній веб-сайт, я завжди обов'язково копіюю відповідну інформацію у свою відповідь тут.
дав

10
Чому ви очікуєте, що модифікатор while буде оцінений до того, як вміст розпочнеться ... закінчиться? Ось так і робити. Тим часом петлі повинні працювати. І чому ви були б "щасливі, що більше ніколи не побачите цю ідіому"? Що не так з ним? Я збентежений.
bergie3000

2
begin ... end виглядає як блок, аналогічно {...}. Нічого поганого в цьому немає.
Віктор Пудєєв

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?

Довідка: Прихована Рубі {{} петля ()


1
хе, побили його. чорт.
Blorgbeard вийшов

Чи не додасть цей код порожній рядок до масиву, якщо немає вводу?
AndrewR

1
Хоча це не стосується тут, одна проблема конструкції початку-кінця-час полягає в тому, що, на відміну від усіх інших конструкцій рубіну, він не повертає значення останнього виразу: "почати 1 кінець, коли помилково" повертає нуль (не 1, не помилково)
tokland

9
Ви можете використовувати, until info.empty?а не while not info.empty?.
Ендрю Грімм

Насправді @AndrewR - це суть справи .. робити речі перед порівнянням .. робити diplay ("залишився = # {count}), поки (count> 0) ... я отримую відображення" залишився = 0 ".. ідеально!
baash05

45

Як щодо цього?

people = []

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

4
Приємно, набагато елегантніше.
Blorgbeard вийшов

23
Але це не цикл "робити ... поки". :)
Олександр Прокоф'єв,

4
Але це робить те саме в цьому випадку, якщо я не помиляюся
Blorgbeard вийшов

18
@Blorgbeard, цикл do.. while завжди працює один раз, а потім оцінює, чи слід продовжувати працювати. Традиційний цикл while / пока не може працювати 0 разів. Це не велика різниця, але вони різні.
Скотт Сувей

5
@Scott, це правда - я просто мав на увазі, що цей код еквівалентний ОП, хоча він не використовує do / while. Хоча насправді цей код робить половину "циклу" роботи в умові, тож це не зовсім традиційно цикл у той час як - якщо умова не відповідає, деяка робота все одно виконується.
Blorgbeard вийшов

11

Ось повна текстова стаття із мертвого посилання 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

10

Зараз це правильно працює:

begin
    # statment
end until <condition>

Але, можливо, це буде видалено в майбутньому, оскільки це beginтвердження є протизаконним. Дивіться: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/6745

Мац (Творець Рубі) рекомендував робити це так:

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

6

З того, що я збираю, Матц не любить конструкцію

begin
    <multiple_lines_of_code>
end while <cond>

бо, це семантика інша, ніж

<single_line_of_code> while <cond>

в тому, що перша конструкція виконує код спочатку перед тим, як перевірити умову, а друга конструкція спочатку перевіряє умову перед тим, як виконувати код (якщо він колись є). Я вважаю, що Матц вважає за краще зберігати другу конструкцію, оскільки вона відповідає одній строковій конструкції операторів 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

Я не знаю, чи будуть ці пропозиції розбиратися з рештою мови. Але в будь-якому випадку я вважаю за краще дотримуватися виконання зліва направо, а також мовної послідовності.


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

3
це трохи схоже на гото. Код підтверджує ваш намір.
Герхард

Мені це чудово виглядає, хіба що 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
це трохи схоже на гото. Код обґрунтовує ваш намір і виглядає дуже незвично.
Герхард

obfuscate * не затуманити.
qedk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.