Quelle est la "bonne" façon d'itérer à travers un tableau dans Ruby?


341

PHP, pour toutes ses verrues, est assez bon sur ce plan. Il n'y a pas de différence entre un tableau et un hachage (peut-être que je suis naïf, mais cela me semble évidemment juste), et pour répéter soit vous faites simplement

foreach (array/hash as $key => $value)

Dans Ruby, il existe de nombreuses façons de faire ce genre de chose:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Les hachages ont plus de sens, car j'utilise toujours

hash.each do |key, value|

Pourquoi ne puis-je pas faire cela pour les tableaux? Si je veux me souvenir d'une seule méthode, je suppose que je peux l'utiliser each_index(car elle rend à la fois l'index et la valeur disponibles), mais c'est ennuyeux d'avoir à faire array[index]au lieu de simplement value.


Ah oui, j'ai oublié array.each_with_index. Cependant, celui-ci est nul car il va |value, key|et hash.eachvient |key, value|! N'est-ce pas fou?


1
Je suppose que array#each_with_indexutilise |value, key|parce que le nom de la méthode implique l'ordre, alors que l'ordre utilisé pour hash#eachimite la hash[key] = valuesyntaxe?
Benjineer

3
Si vous ne faites que commencer avec les boucles dans Ruby, alors vérifiez en utilisant sélectionner, rejeter, collecter, injecter et détecter
Matthew Carriere

Réponses:


560

Cela va parcourir tous les éléments:

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

Tirages:

1
2
3
4
5
6

Cela va parcourir tous les éléments en vous donnant la valeur et l'index:

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

Tirages:

A => 0
B => 1
C => 2

Je ne suis pas tout à fait sûr de votre question laquelle vous recherchez.


1
Hors sujet, je ne trouve pas each_with_index dans ruby ​​1.9 dans le tableau
CantGetANick

19
@CantGetANick: La classe Array inclut le module Enumerable qui a cette méthode.
xuinkrbin.

88

Je pense qu'il n'y a pas qu'une seule bonne façon. Il existe de nombreuses façons différentes d'itérer, et chacune a sa propre niche.

  • each est suffisant pour de nombreux usages, car je ne me soucie pas souvent des index.
  • each_ with _index agit comme Hash # chacun - vous obtenez la valeur et l'index.
  • each_index- juste les index. Je ne l'utilise pas souvent. Équivalent à "length.times".
  • map est une autre façon d'itérer, utile lorsque vous souhaitez transformer un tableau en un autre.
  • select est l'itérateur à utiliser lorsque vous souhaitez choisir un sous-ensemble.
  • inject est utile pour générer des sommes ou des produits, ou pour collecter un seul résultat.

Cela peut sembler beaucoup de choses à retenir, mais ne vous inquiétez pas, vous pouvez vous en tirer sans les connaître toutes. Mais au fur et à mesure que vous commencez à apprendre et à utiliser les différentes méthodes, votre code deviendra plus propre et plus clair, et vous serez en route vers la maîtrise de Ruby.


très bonne réponse! Je voudrais mentionner l'inverse comme #reject et les alias comme #collect.
Emily Tui Ch

60

Je ne dis pas que Array-> |value,index|et Hash-> |key,value|n'est pas fou (voir le commentaire d'Horace Loeb), mais je dis qu'il y a une manière saine d'attendre cet arrangement.

Lorsque je traite des tableaux, je me concentre sur les éléments du tableau (pas sur l'index car l'index est transitoire). La méthode est chacun avec index, c'est-à-dire chaque + index, ou | chaque, index |, ou |value,index|. Ceci est également cohérent avec l'index considéré comme un argument facultatif, par exemple | valeur | est équivalent à | value, index = nil | ce qui est cohérent avec | value, index |.

Lorsque je traite des hachages, je suis souvent plus concentré sur les clés que sur les valeurs, et je traite généralement les clés et les valeurs dans cet ordre, soit key => valueou hash[key] = value.

Si vous voulez taper du canard, alors utilisez explicitement une méthode définie comme l'a montré Brent Longborough, ou une méthode implicite comme l'a montré maxhawkins.

Ruby est tout au sujet de l'adaptation de la langue au programmeur, pas du programmeur de l'adaptation à la langue. C'est pourquoi il y a tant de façons. Il y a tellement de façons de penser à quelque chose. Dans Ruby, vous choisissez le plus proche et le reste du code est généralement extrêmement soigné et concis.

Quant à la question d'origine, "Quelle est la" bonne "façon d'itérer à travers un tableau dans Ruby?", Eh bien, je pense que la façon principale (c'est-à-dire sans sucre syntaxique puissant ou puissance orientée objet) est de faire:

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

Mais Ruby est tout sur le sucre syntaxique puissant et la puissance orientée objet, mais de toute façon voici l'équivalent pour les hachages, et les clés peuvent être commandées ou non:

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

Donc, ma réponse est: "La" bonne "façon de parcourir un tableau dans Ruby dépend de vous (c'est-à-dire du programmeur ou de l'équipe de programmation) et du projet.". Le meilleur programmeur Ruby fait le meilleur choix (quelle puissance syntaxique et / ou quelle approche orientée objet). Le meilleur programmeur Ruby continue de chercher d'autres moyens.


Maintenant, je veux poser une autre question, "Quelle est la" bonne "façon de parcourir une plage en Ruby à l'envers?"! (Cette question est de savoir comment je suis arrivé sur cette page.)

C'est agréable à faire (pour les attaquants):

(1..10).each{|i| puts "i=#{i}" }

mais je n'aime pas faire (pour l'arrière):

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

Eh bien, cela ne me dérange pas vraiment de faire ça trop, mais quand j'enseigne à reculer, je veux montrer à mes élèves une symétrie sympa (c.-à-d. Avec une différence minimale, par exemple en ajoutant seulement un inverse ou un pas -1, mais sans modifier quoi que ce soit d'autre). Vous pouvez faire (pour la symétrie):

(a=*1..10).each{|i| puts "i=#{i}" }

et

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

ce que je n'aime pas beaucoup, mais tu ne peux pas faire

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" }   # I don't want this though.  It's dangerous

Vous pourriez finalement faire

class Range

  def each_reverse(&block)
    self.to_a.reverse.each(&block)
  end

end

mais je veux enseigner du Ruby pur plutôt que des approches orientées objet (pour l'instant). Je voudrais répéter en arrière:

  • sans créer de tableau (considérez 0..1000000000)
  • travailler pour n'importe quelle gamme (par exemple, les chaînes, pas seulement les entiers)
  • sans utiliser de puissance supplémentaire orientée objet (c'est-à-dire sans modification de classe)

Je pense que cela est impossible sans définir une predméthode, ce qui signifie modifier la classe Range pour l'utiliser. Si vous pouvez le faire, veuillez me le faire savoir, sinon une confirmation de l'impossibilité serait appréciée, mais ce serait décevant. Peut-être que Ruby 1.9 y répond.

(Merci d'avoir pris le temps de lire ceci.)


5
1.upto(10) do |i| puts i endet 10.downto(1) do puts i enddonne en quelque sorte la symétrie que vous vouliez. J'espère que cela pourra aider. Je ne sais pas si cela fonctionnerait pour les chaînes et autres.
Suren

(1..10) .to_a.sort {| x, y | y <=> x} .chaque {| i | met "i = # {i}"} - le revers est plus lent.
Davidslv

Tu peux [*1..10].each{|i| puts "i=#{i}" }.
Hauleth

19

Utilisez each_with_index lorsque vous avez besoin des deux.

ary.each_with_index { |val, idx| # ...

12

Les autres réponses sont très bien, mais je voulais souligner une autre chose périphérique: les tableaux sont ordonnés, tandis que les hachages ne sont pas en 1.8. (Dans Ruby 1.9, les hachages sont ordonnés par ordre d'insertion des clés.) Il ne serait donc pas judicieux avant 1.9 d'itérer sur un hachage de la même manière / séquence que les tableaux, qui ont toujours eu un ordre défini. Je ne sais pas quel est l'ordre par défaut pour les tableaux associatifs PHP (apparemment, mon google fu n'est pas assez fort pour le comprendre non plus), mais je ne sais pas comment vous pouvez considérer les tableaux PHP réguliers et les tableaux associatifs PHP pour être "le même" dans ce contexte, car l'ordre des tableaux associatifs semble indéfini.

En tant que tel, la méthode Ruby me semble plus claire et intuitive. :)


1
les hachages et les tableaux sont la même chose! les tableaux mappent les entiers aux objets et les hachages mappent les objets aux objets. les tableaux ne sont que des cas particuliers de hachage, non?
Tom Lehman

1
Comme je l'ai dit, un tableau est un ensemble ordonné. Un mappage (au sens générique) n'est pas ordonné. Si vous limitez le jeu de clés aux entiers (comme avec les tableaux), il se trouve que le jeu de clés a un ordre. Dans un mappage générique (Hash / Tableau associatif), les clés peuvent ne pas avoir d'ordre.
Pistos

@Horace: les hachages et les tableaux ne sont pas identiques. Si l'un est un cas particulier de l'autre, ils ne peuvent pas être les mêmes. Mais pire encore, les tableaux ne sont pas un type de hachage spécial, ce n'est qu'une façon abstraite de les visualiser. Comme l'a souligné Brent ci-dessus, l'utilisation de hachages et de tableaux de manière interchangeable peut indiquer une odeur de code.
Zane

9

Essayer de faire la même chose de manière cohérente avec les tableaux et les hachages pourrait juste être une odeur de code, mais, au risque d'être considéré comme un demi-patcher codor, si vous recherchez un comportement cohérent, cela ferait-il l'affaire? ?:

class Hash
    def each_pairwise
        self.each { | x, y |
            yield [x, y]
        }
    end
end

class Array
    def each_pairwise
        self.each_with_index { | x, y |
            yield [y, x]
        }
    end
end

["a","b","c"].each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

7

Voici les quatre options énumérées dans votre question, organisées par liberté de contrôle. Vous voudrez peut-être en utiliser un autre en fonction de vos besoins.

  1. Passez simplement par des valeurs:

    array.each
  2. Parcourez simplement les indices:

    array.each_index
  3. Passer par les indices + variable d'index:

    for i in array
  4. Nombre de boucles de contrôle + variable d'index:

    array.length.times do | i |

5

J'avais essayé de créer un menu (dans Camping et Markaby ) en utilisant un hachage.

Chaque élément a 2 éléments: une étiquette de menu et une URL , donc un hachage semblait correct, mais l'URL '/' pour 'Accueil' apparaissait toujours en dernier (comme vous vous y attendriez pour un hachage), donc les éléments de menu apparaissaient mal ordre.

L'utilisation d'un tableau avec each_slicefait le travail:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

Ajouter des valeurs supplémentaires pour chaque élément de menu (par exemple, comme un nom d' ID CSS ) signifie simplement augmenter la valeur de tranche. Donc, comme un hachage mais avec des groupes composés d'un certain nombre d'éléments. Parfait.

Donc, c'est juste pour dire merci d'avoir fait allusion par inadvertance à une solution!

Évident, mais mérite d'être mentionné: je suggère de vérifier si la longueur du tableau est divisible par la valeur de la tranche.


4

Si vous utilisez le mixage énumérable (comme le fait Rails), vous pouvez faire quelque chose de similaire à l'extrait de php répertorié. Utilisez simplement la méthode each_slice et aplatissez le hachage.

require 'enumerator' 

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

Moins de patchs de singe requis.

Cependant, cela pose des problèmes lorsque vous avez un tableau récursif ou un hachage avec des valeurs de tableau. Dans ruby ​​1.9, ce problème est résolu avec un paramètre de la méthode d'aplatissement qui spécifie la profondeur de récurrence.

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

Quant à la question de savoir s'il s'agit d'une odeur de code, je ne suis pas sûr. Habituellement, lorsque je dois me pencher en arrière pour répéter quelque chose, je prends du recul et je réalise que j'attaque mal le problème.


2

La bonne façon est celle avec laquelle vous vous sentez le plus à l'aise et qui fait ce que vous voulez qu'elle fasse. Dans la programmation, il y a rarement une façon «correcte» de faire les choses, plus souvent il y a plusieurs façons de choisir.

Si vous êtes à l'aise avec certaines façons de faire les choses, faites-le, à moins que cela ne fonctionne pas - alors il est temps de trouver une meilleure façon.


2

Dans Ruby 2.1, la méthode each_with_index est supprimée. Au lieu de cela, vous pouvez utiliser each_index

Exemple:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

produit:

0 -- 1 -- 2 --

4
Ce n'est pas vrai. Comme indiqué dans les commentaires de la réponse acceptée, la raison each_with_indexn'apparaît pas dans la documentation car elle est fournie par le Enumerablemodule.
ihaztehcodez

0

Utiliser la même méthode pour itérer à travers les tableaux et les hachages est logique, par exemple pour traiter des structures de hachage et de tableaux imbriquées résultant souvent d'analyseurs, de la lecture de fichiers JSON, etc.

Une façon intelligente qui n'a pas encore été mentionnée est de savoir comment cela se fait dans la bibliothèque Ruby Facets d'extensions de bibliothèque standard. D' ici :

class Array

  # Iterate over index and value. The intention of this
  # method is to provide polymorphism with Hash.
  #
  def each_pair #:yield:
    each_with_index {|e, i| yield(i,e) }
  end

end

Il existe déjà Hash#each_pairun alias de Hash#each. Donc, après ce correctif, nous l'avons également Array#each_pairet pouvons l'utiliser de manière interchangeable pour parcourir les hachages et les tableaux. Cela corrige la folie observée de l'OP qui Array#each_with_indexa les arguments de bloc inversés par rapport à Hash#each. Exemple d'utilisation:

my_array = ['Hello', 'World', '!']
my_array.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"

my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
my_hash.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.