Existe um loop "faça ... enquanto" em Ruby?


453

Estou usando esse código para permitir que o usuário digite nomes enquanto o programa os armazena em uma matriz até que eles digitam uma string vazia (eles devem pressionar enter após cada nome):

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

Esse código ficaria muito melhor em um loop do ... while:

people = []

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

Nesse código, não preciso atribuir informações a uma sequência aleatória.

Infelizmente, esse tipo de loop parece não existir no Ruby. Alguém pode sugerir uma maneira melhor de fazer isso?


1
Eu acho que o loop while normal parece mais agradável e é mais fácil de ler.
Magne

1
@ Jeremy Ruten, existe alguma chance de você estar interessado em alterar a resposta aceita para a resposta de Siwei Shen loop do; ...; break if ...; end?
David Winiecki

Respostas:


644

ATENÇÃO :

O begin <code> end while <condition>argumento é rejeitado pelo autor de Ruby, Matz. Em vez disso, ele sugere usar Kernel#loop, por exemplo,

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

Aqui está uma troca de e-mails em 23 de novembro de 2005, onde Matz declara:

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

O wiki do RosettaCode tem uma história semelhante:

Em novembro de 2005, Yukihiro Matsumoto, o criador do Ruby, lamentou esse recurso de loop e sugeriu o uso do Kernel # loop.


18
Spot on. Este begin end whilemétodo não parecia certo. Obrigado por me dar a forragem de que precisava para convencer o resto da equipe.
Joshua Pinter

2
Parece que o loop begin-end-while está realmente avaliando a condição antes de executar o loop. A diferença entre isso e um loop while regular é que ele é garantido para executar pelo menos uma vez. É perto o suficiente para fazer ... enquanto para causar problemas.
user1992284

4
Então, até onde eu entendi bem, o começo e o fim são "arrependidos" porque violam a semântica dos modificadores, ou seja: eles são verificados antes de executar o bloco, por exemplo: coloca k se! K.nil ?. Aqui 'if' é um 'modificador': ele é verificado ANTES, para determinar se a instrução 'puts k' é executada. !), são avaliados APÓS a primeira execução. Talvez isso tenha causado arrependimentos, mas temos algo 'mais forte' do que um post antigo do fórum, uma espécie de depreciação oficial como costuma ser em muitas outras ocasiões?
AgostinoX

2
Levei um tempo embaraçoso para descobrir por que meu uso desse loop ruby ​​'do-while' não estava funcionando. Você deve usar 'a menos que' para imitar mais de perto o estilo c, enquanto não pode acabar como eu e esquecer de inverter a condição: p
Connor Clark

1
@ James De acordo com o correio vinculado, ele disse que estava "lamentando" adicioná-lo. Às vezes, as pessoas cometem erros, mesmo que sejam designers de linguagem.
Xiong Chiamiov

188

Encontrei o seguinte trecho ao ler a fonte Tempfile#initializena biblioteca principal do 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)

À primeira vista, assumi que o modificador while seria avaliado antes do conteúdo de begin ... end, mas esse não é o caso. Observar:

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

Como seria de esperar, o loop continuará sendo executado enquanto o modificador for verdadeiro.

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

Enquanto eu ficaria feliz em nunca mais ver esse idioma, comece ... o fim é bastante poderoso. A seguir, é apresentado um idioma comum para memorizar um método de uma linha sem parâmetros:

def expensive
  @expensive ||= 2 + 2
end

Aqui está uma maneira feia, mas rápida, de memorizar algo mais complexo:

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

Originalmente escrito por Jeremy Voorhis . O conteúdo foi copiado aqui porque parece ter sido retirado do site de origem. Cópias também podem ser encontradas no Web Archive e no Ruby Buzz Forum . -Bill the Lizard



56
É por isso que, ao criar um link para um site externo, sempre copio as informações relevantes em minha resposta aqui.
davr 9/09/10

10
Por que você esperaria que o modificador while fosse avaliado antes do conteúdo de begin ... end? É assim que acontece ... enquanto os loops devem funcionar. E por que você "ficaria feliz em nunca mais ver esse idioma?" O que há de errado com isso? Estou confuso.
bergie3000

2
begin ... end parece um bloco, da mesma forma {...}. Nada de errado com isso.
Victor Pudeyev

1
-1: A resposta de Siwei Shen explica que isso begin .. endé um tanto desaprovado. Use em loop do .. break if <condition>vez disso.
18713 Kevin

102

Como isso:

people = []

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

Referência: Ruby Hidden do {} while () Loop


1
heh, derrotado por isso. Droga.
Blorgbeard sai em 25/09/08

Esse código não adicionará uma string vazia à matriz se não houver entrada?
AndrewR 25/09/08

1
Embora não se aplique aqui, um problema da construção begin-end-while é que, diferentemente de todas as outras construções ruby, ele não retorna o valor da última expressão: "begin 1 end while false" retorna nulo (não 1, Não é falso)
tokland

9
Você pode usar em until info.empty?vez de while not info.empty?.
Andrew Grimm

Na verdade, @AndrewR esse é o ponto .. fazer as coisas antes de uma comparação .. fazer diplay ("restante = # {contagem}) enquanto (contagem> 0) ... Eu recebo uma exibição de" restante = 0 ".. perfeito!
baash05

45

Que tal agora?

people = []

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

4
Bom, muito mais elegante.
Blorgbeard sai

23
Mas esse não é o loop "faça ... enquanto". :)
Alexander Prokofyev

4
Mas faz a mesma coisa neste caso, a menos que eu esteja enganado
Blorgbeard saiu

18
@Blorgbeard, um loop do..time sempre é executado uma vez e depois avalia se ele deve continuar sendo executado. Um loop while / till tradicional pode ser executado 0 vezes. Não é uma enorme diferença, mas eles são diferentes.
Scott Swezey

5
@ Scott, isso é verdade - eu apenas quis dizer que esse código é equivalente ao OP, mesmo que ele não use um tempo de folga. Embora, na verdade, esse código faça metade do "trabalho" do loop na condição, portanto também não é um loop while tradicional - se a condição não corresponder, algum trabalho ainda será feito.
Blorgbeard sai

11

Aqui está o artigo em texto completo do link morto do hubbardr para o meu blog.

Encontrei o seguinte trecho ao ler a fonte Tempfile#initializena biblioteca principal do 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)

À primeira vista, presumi que o whilemodificador seria avaliado antes do conteúdo de begin...end, mas esse não é o caso. Observar:

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

Como seria de esperar, o loop continuará sendo executado enquanto o modificador for verdadeiro.

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

Enquanto eu ficaria feliz em nunca ver esse idioma novamente, begin...endé bastante poderoso. A seguir, é apresentado um idioma comum para memorizar um método de uma linha sem parâmetros:

def expensive
  @expensive ||= 2 + 2
end

Aqui está uma maneira feia, mas rápida, de memorizar algo mais complexo:

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


6

Pelo que entendi, Matz não gosta da construção

begin
    <multiple_lines_of_code>
end while <cond>

porque, é semântica é diferente de

<single_line_of_code> while <cond>

em que a primeira construção executa o código primeiro antes de verificar a condição e a segunda construção testa a condição primeiro antes de executar o código (se houver). Suponho que Matz prefere manter o segundo construto porque ele corresponde a um construto de linha das instruções if.

Eu nunca gostei da segunda construção, mesmo para declarações if. Em todos os outros casos, o computador executa o código da esquerda para a direita (por exemplo, || e &&) de cima para baixo. Os seres humanos leem o código da esquerda para a direita, de cima para baixo.

Sugiro as seguintes construções:

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

Não sei se essas sugestões serão analisadas com o restante do idioma. Mas, em qualquer caso, prefiro manter a execução da esquerda para a direita, além da consistência da linguagem.


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

3
isso parece um pouco. O código confunde sua intenção.
Gerhard

Parece ótimo para mim, exceto que while truepode ser substituído por loop do.
David Winiecki

@DavidWiniecki, de fato, while truepode ser substituído por loop do. Mas testei as duas construções com muitas iterações dentro do loop e descobri que while trueé pelo menos duas vezes mais rápido que loop do. Não posso explicar a diferença, mas está definitivamente lá. (Descoberto ao testar o Advent of Code 2017, dia 15.)
Jochem Schulenklopper 15/17

2

Aqui está mais um:

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

Eu prefiro isso, pois ele unlessestá logo no início e não leio um monte de código (que pode ser mais do que mostrado aqui) apenas para encontrar um 'pendente' unlessno final. É um princípio geral no código que o modificador e as condições são mais fáceis de usar quando estão "na frente" assim.
Michael Durrant

Às vezes, eu gostaria que os programadores tivessem que pagar em dinheiro por cada comparação extra. E que a "aparência" para nós era menos importante do que a aparência que a usa um milhão de vezes por dia.
baash05

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

2
isso parece um pouco. O código confunde sua intenção e parece muito desleixado.
Gerhard

ofuscar * não ofuscar.
Qedk 27/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.