web-dev-qa-db-fra.com

Y a-t-il une raison pour laquelle nous ne pouvons pas itérer sur "Reverse Range" en rubis?

J'ai essayé d'itérer en arrière en utilisant une plage et each:

(4..0).each do |i|
  puts i
end
==> 4..0

Itération à travers 0..4 écrit les chiffres. Sur l'autre gamme r = 4..0 semble aller bien, r.first == 4, r.last == 0.

Il me semble étrange que la construction ci-dessus ne produise pas le résultat attendu. Quelle en est la raison? Quelles sont les situations où ce comportement est raisonnable?

96
fifigyuri

Une plage n'est que cela: quelque chose défini par son début et sa fin, pas par son contenu. "Itérer" sur une plage n'a pas vraiment de sens dans un cas général. Considérez, par exemple, comment vous "itéreriez" sur la plage produite par deux dates. Souhaitez-vous répéter le jour? par mois? par an? par semaine? Ce n'est pas bien défini. OMI, le fait qu'il soit autorisé pour les plages avant doit être considéré uniquement comme une méthode de commodité.

Si vous souhaitez parcourir en arrière sur une plage comme celle-ci, vous pouvez toujours utiliser downto:

$ r = 10..6
=> 10..6

$ (r.first).downto(r.last).each { |i| puts i }
10
9
8
7
6

Voici quelques réflexions supplémentaires des autres sur pourquoi il est difficile à la fois d'autoriser l'itération et de traiter systématiquement les plages inverses.

90
John Feminella

Que diriez-vous (0..1).reverse_each qui itère la plage en arrière?

90
Marko Taponen

Itérer sur une plage dans Ruby avec each appelle la méthode succ sur le premier objet de la plage.

$ 4.succ
=> 5

Et 5 est en dehors de la plage.

Vous pouvez simuler l'itération inverse avec ce hack:

(-4..0).each { |n| puts n.abs }

John a souligné que cela ne fonctionnera pas s'il s'étend sur 0. Cela:

>> (-2..2).each { |n| puts -n }
2
1
0
-1
-2
=> -2..2

Je ne peux pas dire que j'aime vraiment l'un d'eux parce qu'ils obscurcissent l'intention.

18
Jonas Elfström

Selon le livre "Programming Ruby", l'objet Range stocke les deux points de terminaison de la plage et utilise le .succ membre pour générer les valeurs intermédiaires. Selon le type de type de données que vous utilisez dans votre plage, vous pouvez toujours créer une sous-classe de Integer et redéfinir le .succ membre pour qu'il agisse comme un itérateur inversé (vous voudrez probablement aussi redéfinir .next ainsi que).

Vous pouvez également obtenir les résultats que vous recherchez sans utiliser de plage. Essaye ça:

4.step(0, -1) do |i|
    puts i
end

Cela passera de 4 à 0 par pas de -1. Cependant, je ne sais pas si cela fonctionnera pour autre chose que des arguments entiers.

12
bta

Une autre façon est (1..10).to_a.reverse

10
Sergey Kishenin

Vous pouvez même utiliser une boucle for:

for n in 4.downto(0) do
  print n
end

qui imprime:

4
3
2
1
0
4
bakerstreet221b

si la liste n'est pas si grande. je pense [*0..4].reverse.each { |i| puts i } est le moyen le plus simple.

3
marocchino

Comme l'a dit bta, la raison en est que Range#each envoie succ à son début, puis au résultat de cet appel succ, et ainsi de suite jusqu'à ce que le résultat soit supérieur à la valeur de fin. Vous ne pouvez pas passer de 4 à 0 en appelant succ, et en fait vous commencez déjà plus haut que la fin.

1
Chuck

J'ajoute une autre possibilité comment réaliser l'itération sur une plage inversée. Je ne l'utilise pas, mais c'est une possibilité. Il est un peu risqué de patcher les singes Ruby objets principaux.

class Range

  def each(&block)
    direction = (first<=last ? 1 : -1)
    i = first
    not_reached_the_end = if first<=last
                            lambda {|i| i<=last}
                          else
                            lambda {|i| i>=last}
                          end
    while not_reached_the_end.call(i)
      yield i
      i += direction
    end
  end
end
1
fifigyuri

L'OP a écrit

Il me semble étrange que la construction ci-dessus ne produise pas le résultat attendu. Quelle en est la raison? Quelles sont les situations où ce comportement est raisonnable?

pas 'Peut-il être fait?' mais pour répondre à la question qui n'a pas été posée avant d'arriver à la question qui a été effectivement posée:

$ irb
2.1.5 :001 > (0..4)
 => 0..4
2.1.5 :002 > (0..4).each { |i| puts i }
0
1
2
3
4
 => 0..4
2.1.5 :003 > (4..0).each { |i| puts i }
 => 4..0
2.1.5 :007 > (0..4).reverse_each { |i| puts i }
4
3
2
1
0
 => 0..4
2.1.5 :009 > 4.downto(0).each { |i| puts i }
4
3
2
1
0
 => 4

Puisque reverse_each est censé construire un tableau entier, downto va clairement être plus efficace. Le fait qu'un concepteur de langage puisse même envisager d'implémenter des choses comme cela est un peu lié à la réponse à la question réelle telle qu'elle est posée.

Pour répondre à la question telle qu'elle a effectivement été posée ...

La raison en est que Ruby est un langage sans cesse surprenant. Certaines surprises sont agréables, mais il y a beaucoup de comportements qui sont carrément brisés. Même si certains de ces exemples suivants sont corrigés par des versions plus récentes, il y en a beaucoup d'autres, et ils restent des actes d'accusation dans l'état d'esprit du design original:

nil.to_s
   .to_s
   .inspect

donne "" mais

nil.to_s
#  .to_s   # Don't want this one for now
   .inspect

résulte en

 syntax error, unexpected '.', expecting end-of-input
 .inspect
 ^

Vous vous attendez probablement à ce que << et Push soient les mêmes pour l'ajout aux tableaux, mais

a = []
a << *[:A, :B]    # is illegal but
a.Push *[:A, :B]  # isn't.

Vous vous attendriez probablement à ce que "grep" se comporte comme son équivalent en ligne de commande Unix, mais il ne correspond pas à === pas = ~, malgré son nom.

$ echo foo | grep .
foo
$ Ruby -le 'p ["foo"].grep(".")'
[]

Diverses méthodes sont alias de façon inattendue, vous devez donc apprendre plusieurs noms pour la même chose - par exemple find et detect - même si vous aimez la plupart des développeurs et n'utilisez que l'un ou l'autre. Il en va de même pour size, count et length, à l'exception des classes qui les définissent différemment ou n'en définissent pas du tout un ou deux.

À moins que quelqu'un n'ait implémenté autre chose - comme la méthode principale tap a été redéfinie dans diverses bibliothèques d'automatisation pour appuyer sur quelque chose à l'écran. Bonne chance pour savoir ce qui se passe, surtout si un module requis par un autre module a encore utilisé un autre module pour faire quelque chose de non documenté.

L'objet variable d'environnement, ENV ne prend pas en charge la "fusion", vous devez donc écrire

 ENV.to_h.merge('a': '1')

En prime, vous pouvez même redéfinir vos constantes ou celles de quelqu'un d'autre si vous changez d'avis sur ce qu'elles devraient être.

0
android.weasel

Cela a fonctionné pour mon cas d'utilisation paresseux

(-999999..0).lazy.map{|x| -x}.first(3)
#=> [999999, 999998, 999997]
0
forforf