web-dev-qa-db-fra.com

Comment briser le cycle externe en rubis?

En Perl, il existe une possibilité de rompre un cycle externe comme celui-ci:

AAA: for my $stuff (@otherstuff) {
         for my $foo (@bar) {
             last AAA if (somethingbad());
         }
      }

(la syntaxe peut être fausse), qui utilise un libellé de boucle pour séparer la boucle externe de la boucle interne. Y a-t-il quelque chose de similaire dans Ruby?

53
Fluffy

Ce que vous voulez, c'est un flux de contrôle non local, pour lequel Ruby dispose de plusieurs options:

  • Les continuations, 
  • Les exceptions et
  • throwcatch

Continuations

Avantages:

  • Les continuations sont le mécanisme standard pour les flux de contrôle non locaux. En fait, vous pouvez construire _/tout contrôle-flux non local (sous-routines, procédures, fonctions, méthodes, coroutines, machines d'état, générateurs, conditions, exceptions): ils sont à peu près les plus beaux jumeaux de GOTO.

Les inconvénients:

  • Les continuations ne sont pas une partie obligatoire de la spécification de langage Ruby, ce qui signifie que certaines implémentations (XRuby, JRuby, Ruby.NET, IronRuby) ne les implémentent pas. Donc, vous ne pouvez pas compter sur eux.

Exceptions

Avantages:

  • Un article prouve mathématiquement que les exceptions peuvent être plus puissantes que les continuations. IOW: ils peuvent faire tout ce que les poursuites peuvent faire, et plus, pour que vous puissiez les utiliser en remplacement des poursuites.
  • Les exceptions sont universellement disponibles.

Les inconvénients:

  • On les appelle des "exceptions", ce qui fait penser aux gens qu'ils ne le sont "que dans des circonstances exceptionnelles". Cela signifie trois choses: quelqu'un qui lit votre code peut ne pas le comprendre, l'implémentation n'est peut-être pas optimisée (et, oui, les exceptions sont lentes [dans] presque toutes les implémentations de Ruby) et pire, vous tomberez malade De toutes ces personnes, babillant sans réfléchir, «les exceptions ne sont que dans des circonstances exceptionnelles», dès qu’elles jettent un coup d’œil sur votre code. (Bien sûr, ils n'essayeront même pas de comprendre ce que vous faites.)

throwcatch

C'est à peu près à quoi ça ressemblerait:

catch :aaa do
  stuff.each do |otherstuff|
    foo.each do |bar|
      throw :aaa if somethingbad
    end
  end
end

Avantages:

  • Le même que les exceptions.
  • En Ruby 1.9, l'utilisation d'exceptions pour control-flow fait en réalité _/une partie de la spécification de langue! Les boucles, les énumérateurs, les itérateurs et autres utilisent une exception StopIteration pour la résiliation.

Les inconvénients:

  • La communauté Ruby les déteste encore plus que l’utilisation d’exceptions pour control-flow.
36
Jörg W Mittag

Considérons throw/catch . Normalement, la boucle extérieure dans le code ci-dessous sera exécutée cinq fois, mais avec un lancer, vous pouvez la changer comme bon vous semble, en la cassant dans le processus. Considérez ce code Ruby parfaitement valide:

catch (:done) do
  5.times { |i|
    5.times { |j|
      puts "#{i} #{j}"
      throw :done if i + j > 5
    }
  }
end
105
Chris Bunch

Non, il n'y en a pas.

Vos options sont:

  • mettre la boucle dans une méthode et utiliser return pour rompre avec la boucle externe
  • définir ou renvoyer un indicateur de la boucle interne, puis vérifier cet indicateur dans la boucle externe et le rompre lorsque l'indicateur est défini (ce qui est un peu fastidieux)
  • utiliser lancer/attraper pour sortir de la boucle
29
sepp2k
while c1
 while c2
    do_break=true
 end
 next if do_break
end

ou "pause si do_break" en fonction de ce que vous voulez

4
Anno2001

C'est peut-être ce que tu veux? (pas testé)

stuff.find do |otherstuff|
  foo.find do
    somethingbad() && AAA
  end
end

La méthode find continue de boucler jusqu'à ce que le bloc renvoie une valeur non NULL ou que la fin de la liste soit atteinte.

2
Jeff Waltzer

Je sais que je vais le regretter le matin, mais le simple fait d’utiliser une boucle while pourrait suffire.

x=0
until x==10
  x+=1
  y=0
  until y==10
    y+=1
    if y==5 && x==3
      x,y=10,10
    end
  end
  break if x==10
  puts x
end

Le if y==5 && x==3 est seulement un exemple d'expression qui devient vraie.

0
Jonas Elfström

Enrouler une méthode interne autour des boucles pourrait faire l'affaire Exemple:

test = [1,2,3]
test.each do |num|
  def internalHelper
    for i in 0..3 
      for j in 0..3
        puts "this should happen only 3 times"
        if true
          return
        end
      end
    end
  end
internalHelper
end

Ici, vous pouvez vérifier toutes les boucles for et revenir de la méthode interne une fois la condition remplie.

0
Metareven

Vous pouvez envisager d'ajouter un indicateur, placé à l'intérieur de la boucle interne, pour contrôler la boucle externe.

'next' la boucle externe

for i in (1 .. 5)
  next_outer_loop = false
  for j in (1 .. 5)
    if j > i     
      next_outer_loop = true if j % 2 == 0
      break      
    end          
    puts "i: #{i}, j: #{j}"
  end            
  print "i: #{i} "                                                                                                                                                                             
  if next_outer_loop
    puts "with 'next'"
    next         
  end            
  puts "withOUT 'next'"
end

'casser' la boucle externe

for i in (1 .. 5)
  break_outer_loop = false
  for j in (1 .. 5)
    if j > i
      break_outer_loop = true if i > 3
      break
    end
    puts "i: #{i}, j: #{j}"
  end
  break if break_outer_loop
  puts "i: #{i}"
end
0
Stephen LAI